/**
 * TinyMCE version 6.1.2 (2022-07-29)
 */

(function () {
  'use strict'

  var typeOf$1 = function (x) {
    if (x === null) {
      return 'null'
    }
    if (x === undefined) {
      return 'undefined'
    }
    var t = typeof x
    if (t === 'object' && (Array.prototype.isPrototypeOf(x) || (x.constructor && x.constructor.name === 'Array'))) {
      return 'array'
    }
    if (t === 'object' && (String.prototype.isPrototypeOf(x) || (x.constructor && x.constructor.name === 'String'))) {
      return 'string'
    }
    return t
  }
  var isEquatableType = function (x) {
    return ['undefined', 'boolean', 'number', 'string', 'function', 'xml', 'null'].indexOf(x) !== -1
  }

  var sort$1 = function (xs, compareFn) {
    var clone = Array.prototype.slice.call(xs)
    return clone.sort(compareFn)
  }

  var contramap = function (eqa, f) {
    return eq$2(function (x, y) {
      return eqa.eq(f(x), f(y))
    })
  }
  var eq$2 = function (f) {
    return { eq: f }
  }
  var tripleEq = eq$2(function (x, y) {
    return x === y
  })
  var eqString = tripleEq
  var eqArray = function (eqa) {
    return eq$2(function (x, y) {
      if (x.length !== y.length) {
        return false
      }
      var len = x.length
      for (var i = 0; i < len; i++) {
        if (!eqa.eq(x[i], y[i])) {
          return false
        }
      }
      return true
    })
  }
  var eqSortedArray = function (eqa, compareFn) {
    return contramap(eqArray(eqa), function (xs) {
      return sort$1(xs, compareFn)
    })
  }
  var eqRecord = function (eqa) {
    return eq$2(function (x, y) {
      var kx = Object.keys(x)
      var ky = Object.keys(y)
      if (!eqSortedArray(eqString).eq(kx, ky)) {
        return false
      }
      var len = kx.length
      for (var i = 0; i < len; i++) {
        var q = kx[i]
        if (!eqa.eq(x[q], y[q])) {
          return false
        }
      }
      return true
    })
  }
  var eqAny = eq$2(function (x, y) {
    if (x === y) {
      return true
    }
    var tx = typeOf$1(x)
    var ty = typeOf$1(y)
    if (tx !== ty) {
      return false
    }
    if (isEquatableType(tx)) {
      return x === y
    } else if (tx === 'array') {
      return eqArray(eqAny).eq(x, y)
    } else if (tx === 'object') {
      return eqRecord(eqAny).eq(x, y)
    }
    return false
  })

  const getPrototypeOf$1 = Object.getPrototypeOf
  const hasProto = (v, constructor, predicate) => {
    var _a
    if (predicate(v, constructor.prototype)) {
      return true
    } else {
      return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name
    }
  }
  const typeOf = (x) => {
    const t = typeof x
    if (x === null) {
      return 'null'
    } else if (t === 'object' && Array.isArray(x)) {
      return 'array'
    } else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
      return 'string'
    } else {
      return t
    }
  }
  const isType$1 = (type) => (value) => typeOf(value) === type
  const isSimpleType = (type) => (value) => typeof value === type
  const eq$1 = (t) => (a) => t === a
  const is$4 = (value, constructor) =>
    isObject(value) && hasProto(value, constructor, (o, proto) => getPrototypeOf$1(o) === proto)
  const isString = isType$1('string')
  const isObject = isType$1('object')
  const isPlainObject = (value) => is$4(value, Object)
  const isArray$1 = isType$1('array')
  const isNull = eq$1(null)
  const isBoolean = isSimpleType('boolean')
  const isUndefined = eq$1(undefined)
  const isNullable = (a) => a === null || a === undefined
  const isNonNullable = (a) => !isNullable(a)
  const isFunction = isSimpleType('function')
  const isNumber = isSimpleType('number')
  const isArrayOf = (value, pred) => {
    if (isArray$1(value)) {
      for (let i = 0, len = value.length; i < len; ++i) {
        if (!pred(value[i])) {
          return false
        }
      }
      return true
    }
    return false
  }

  const noop = () => {}
  const compose = (fa, fb) => {
    return (...args) => {
      return fa(fb.apply(null, args))
    }
  }
  const compose1 = (fbc, fab) => (a) => fbc(fab(a))
  const constant = (value) => {
    return () => {
      return value
    }
  }
  const identity = (x) => {
    return x
  }
  const tripleEquals = (a, b) => {
    return a === b
  }
  function curry(fn, ...initialArgs) {
    return (...restArgs) => {
      const all = initialArgs.concat(restArgs)
      return fn.apply(null, all)
    }
  }
  const not = (f) => (t) => !f(t)
  const die = (msg) => {
    return () => {
      throw new Error(msg)
    }
  }
  const apply$1 = (f) => {
    return f()
  }
  const call = (f) => {
    f()
  }
  const never = constant(false)
  const always = constant(true)

  class Optional {
    constructor(tag, value) {
      this.tag = tag
      this.value = value
    }
    static some(value) {
      return new Optional(true, value)
    }
    static none() {
      return Optional.singletonNone
    }
    fold(onNone, onSome) {
      if (this.tag) {
        return onSome(this.value)
      } else {
        return onNone()
      }
    }
    isSome() {
      return this.tag
    }
    isNone() {
      return !this.tag
    }
    map(mapper) {
      if (this.tag) {
        return Optional.some(mapper(this.value))
      } else {
        return Optional.none()
      }
    }
    bind(binder) {
      if (this.tag) {
        return binder(this.value)
      } else {
        return Optional.none()
      }
    }
    exists(predicate) {
      return this.tag && predicate(this.value)
    }
    forall(predicate) {
      return !this.tag || predicate(this.value)
    }
    filter(predicate) {
      if (!this.tag || predicate(this.value)) {
        return this
      } else {
        return Optional.none()
      }
    }
    getOr(replacement) {
      return this.tag ? this.value : replacement
    }
    or(replacement) {
      return this.tag ? this : replacement
    }
    getOrThunk(thunk) {
      return this.tag ? this.value : thunk()
    }
    orThunk(thunk) {
      return this.tag ? this : thunk()
    }
    getOrDie(message) {
      if (!this.tag) {
        throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None')
      } else {
        return this.value
      }
    }
    static from(value) {
      return isNonNullable(value) ? Optional.some(value) : Optional.none()
    }
    getOrNull() {
      return this.tag ? this.value : null
    }
    getOrUndefined() {
      return this.value
    }
    each(worker) {
      if (this.tag) {
        worker(this.value)
      }
    }
    toArray() {
      return this.tag ? [this.value] : []
    }
    toString() {
      return this.tag ? `some(${this.value})` : 'none()'
    }
  }
  Optional.singletonNone = new Optional(false)

  const nativeSlice = Array.prototype.slice
  const nativeIndexOf = Array.prototype.indexOf
  const nativePush = Array.prototype.push
  const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t)
  const indexOf$1 = (xs, x) => {
    const r = rawIndexOf(xs, x)
    return r === -1 ? Optional.none() : Optional.some(r)
  }
  const contains$2 = (xs, x) => rawIndexOf(xs, x) > -1
  const exists = (xs, pred) => {
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      if (pred(x, i)) {
        return true
      }
    }
    return false
  }
  const map$3 = (xs, f) => {
    const len = xs.length
    const r = new Array(len)
    for (let i = 0; i < len; i++) {
      const x = xs[i]
      r[i] = f(x, i)
    }
    return r
  }
  const each$f = (xs, f) => {
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      f(x, i)
    }
  }
  const eachr = (xs, f) => {
    for (let i = xs.length - 1; i >= 0; i--) {
      const x = xs[i]
      f(x, i)
    }
  }
  const partition$2 = (xs, pred) => {
    const pass = []
    const fail = []
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      const arr = pred(x, i) ? pass : fail
      arr.push(x)
    }
    return {
      pass,
      fail,
    }
  }
  const filter$6 = (xs, pred) => {
    const r = []
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      if (pred(x, i)) {
        r.push(x)
      }
    }
    return r
  }
  const foldr = (xs, f, acc) => {
    eachr(xs, (x, i) => {
      acc = f(acc, x, i)
    })
    return acc
  }
  const foldl = (xs, f, acc) => {
    each$f(xs, (x, i) => {
      acc = f(acc, x, i)
    })
    return acc
  }
  const findUntil$1 = (xs, pred, until) => {
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      if (pred(x, i)) {
        return Optional.some(x)
      } else if (until(x, i)) {
        break
      }
    }
    return Optional.none()
  }
  const find$2 = (xs, pred) => {
    return findUntil$1(xs, pred, never)
  }
  const findIndex$2 = (xs, pred) => {
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      if (pred(x, i)) {
        return Optional.some(i)
      }
    }
    return Optional.none()
  }
  const flatten = (xs) => {
    const r = []
    for (let i = 0, len = xs.length; i < len; ++i) {
      if (!isArray$1(xs[i])) {
        throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs)
      }
      nativePush.apply(r, xs[i])
    }
    return r
  }
  const bind$3 = (xs, f) => flatten(map$3(xs, f))
  const forall = (xs, pred) => {
    for (let i = 0, len = xs.length; i < len; ++i) {
      const x = xs[i]
      if (pred(x, i) !== true) {
        return false
      }
    }
    return true
  }
  const reverse = (xs) => {
    const r = nativeSlice.call(xs, 0)
    r.reverse()
    return r
  }
  const difference = (a1, a2) => filter$6(a1, (x) => !contains$2(a2, x))
  const mapToObject = (xs, f) => {
    const r = {}
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      r[String(x)] = f(x, i)
    }
    return r
  }
  const sort = (xs, comparator) => {
    const copy = nativeSlice.call(xs, 0)
    copy.sort(comparator)
    return copy
  }
  const get$b = (xs, i) => (i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none())
  const head = (xs) => get$b(xs, 0)
  const last$3 = (xs) => get$b(xs, xs.length - 1)
  const from = isFunction(Array.from) ? Array.from : (x) => nativeSlice.call(x)
  const findMap = (arr, f) => {
    for (let i = 0; i < arr.length; i++) {
      const r = f(arr[i], i)
      if (r.isSome()) {
        return r
      }
    }
    return Optional.none()
  }
  const unique$1 = (xs, comparator) => {
    const r = []
    const isDuplicated = isFunction(comparator) ? (x) => exists(r, (i) => comparator(i, x)) : (x) => contains$2(r, x)
    for (let i = 0, len = xs.length; i < len; i++) {
      const x = xs[i]
      if (!isDuplicated(x)) {
        r.push(x)
      }
    }
    return r
  }

  const keys = Object.keys
  const hasOwnProperty$2 = Object.hasOwnProperty
  const each$e = (obj, f) => {
    const props = keys(obj)
    for (let k = 0, len = props.length; k < len; k++) {
      const i = props[k]
      const x = obj[i]
      f(x, i)
    }
  }
  const map$2 = (obj, f) => {
    return tupleMap(obj, (x, i) => ({
      k: i,
      v: f(x, i),
    }))
  }
  const tupleMap = (obj, f) => {
    const r = {}
    each$e(obj, (x, i) => {
      const tuple = f(x, i)
      r[tuple.k] = tuple.v
    })
    return r
  }
  const objAcc = (r) => (x, i) => {
    r[i] = x
  }
  const internalFilter = (obj, pred, onTrue, onFalse) => {
    const r = {}
    each$e(obj, (x, i) => {
      (pred(x, i) ? onTrue : onFalse)(x, i)
    })
    return r
  }
  const bifilter = (obj, pred) => {
    const t = {}
    const f = {}
    internalFilter(obj, pred, objAcc(t), objAcc(f))
    return {
      t,
      f,
    }
  }
  const filter$5 = (obj, pred) => {
    const t = {}
    internalFilter(obj, pred, objAcc(t), noop)
    return t
  }
  const mapToArray = (obj, f) => {
    const r = []
    each$e(obj, (value, name) => {
      r.push(f(value, name))
    })
    return r
  }
  const values = (obj) => {
    return mapToArray(obj, identity)
  }
  const get$a = (obj, key) => {
    return has$2(obj, key) ? Optional.from(obj[key]) : Optional.none()
  }
  const has$2 = (obj, key) => hasOwnProperty$2.call(obj, key)
  const hasNonNullableKey = (obj, key) => has$2(obj, key) && obj[key] !== undefined && obj[key] !== null
  const equal$1 = (a1, a2, eq = eqAny) => eqRecord(eq).eq(a1, a2)

  const stringArray = (a) => {
    const all = {}
    each$f(a, (key) => {
      all[key] = {}
    })
    return keys(all)
  }

  const isArray = Array.isArray
  const toArray$1 = (obj) => {
    if (!isArray(obj)) {
      const array = []
      for (let i = 0, l = obj.length; i < l; i++) {
        array[i] = obj[i]
      }
      return array
    } else {
      return obj
    }
  }
  const each$d = (o, cb, s) => {
    let n, l
    if (!o) {
      return false
    }
    s = s || o
    if (o.length !== undefined) {
      for (n = 0, l = o.length; n < l; n++) {
        if (cb.call(s, o[n], n, o) === false) {
          return false
        }
      }
    } else {
      for (n in o) {
        if (has$2(o, n)) {
          if (cb.call(s, o[n], n, o) === false) {
            return false
          }
        }
      }
    }
    return true
  }
  const map$1 = (array, callback) => {
    const out = []
    each$d(array, (item, index) => {
      out.push(callback(item, index, array))
    })
    return out
  }
  const filter$4 = (a, f) => {
    const o = []
    each$d(a, (v, index) => {
      if (!f || f(v, index, a)) {
        o.push(v)
      }
    })
    return o
  }
  const indexOf = (a, v) => {
    if (a) {
      for (let i = 0, l = a.length; i < l; i++) {
        if (a[i] === v) {
          return i
        }
      }
    }
    return -1
  }
  const reduce = (collection, iteratee, accumulator, thisArg) => {
    let acc = isUndefined(accumulator) ? collection[0] : accumulator
    for (let i = 0; i < collection.length; i++) {
      acc = iteratee.call(thisArg, acc, collection[i], i)
    }
    return acc
  }
  const findIndex$1 = (array, predicate, thisArg) => {
    let i, l
    for (i = 0, l = array.length; i < l; i++) {
      if (predicate.call(thisArg, array[i], i, array)) {
        return i
      }
    }
    return -1
  }
  const last$2 = (collection) => collection[collection.length - 1]

  const cached = (f) => {
    let called = false
    let r
    return (...args) => {
      if (!called) {
        called = true
        r = f.apply(null, args)
      }
      return r
    }
  }

  const DeviceType = (os, browser, userAgent, mediaMatch) => {
    const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true
    const isiPhone = os.isiOS() && !isiPad
    const isMobile = os.isiOS() || os.isAndroid()
    const isTouch = isMobile || mediaMatch('(pointer:coarse)')
    const isTablet = isiPad || (!isiPhone && isMobile && mediaMatch('(min-device-width:768px)'))
    const isPhone = isiPhone || (isMobile && !isTablet)
    const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false
    const isDesktop = !isPhone && !isTablet && !iOSwebview
    return {
      isiPad: constant(isiPad),
      isiPhone: constant(isiPhone),
      isTablet: constant(isTablet),
      isPhone: constant(isPhone),
      isTouch: constant(isTouch),
      isAndroid: os.isAndroid,
      isiOS: os.isiOS,
      isWebView: constant(iOSwebview),
      isDesktop: constant(isDesktop),
    }
  }

  const firstMatch = (regexes, s) => {
    for (let i = 0; i < regexes.length; i++) {
      const x = regexes[i]
      if (x.test(s)) {
        return x
      }
    }
    return undefined
  }
  const find$1 = (regexes, agent) => {
    const r = firstMatch(regexes, agent)
    if (!r) {
      return {
        major: 0,
        minor: 0,
      }
    }
    const group = (i) => {
      return Number(agent.replace(r, '$' + i))
    }
    return nu$3(group(1), group(2))
  }
  const detect$5 = (versionRegexes, agent) => {
    const cleanedAgent = String(agent).toLowerCase()
    if (versionRegexes.length === 0) {
      return unknown$2()
    }
    return find$1(versionRegexes, cleanedAgent)
  }
  const unknown$2 = () => {
    return nu$3(0, 0)
  }
  const nu$3 = (major, minor) => {
    return {
      major,
      minor,
    }
  }
  const Version = {
    nu: nu$3,
    detect: detect$5,
    unknown: unknown$2,
  }

  const detectBrowser$1 = (browsers, userAgentData) => {
    return findMap(userAgentData.brands, (uaBrand) => {
      const lcBrand = uaBrand.brand.toLowerCase()
      return find$2(browsers, (browser) => {
        var _a
        return lcBrand === ((_a = browser.brand) === null || _a === void 0 ? void 0 : _a.toLowerCase())
      }).map((info) => ({
        current: info.name,
        version: Version.nu(parseInt(uaBrand.version, 10), 0),
      }))
    })
  }

  const detect$4 = (candidates, userAgent) => {
    const agent = String(userAgent).toLowerCase()
    return find$2(candidates, (candidate) => {
      return candidate.search(agent)
    })
  }
  const detectBrowser = (browsers, userAgent) => {
    return detect$4(browsers, userAgent).map((browser) => {
      const version = Version.detect(browser.versionRegexes, userAgent)
      return {
        current: browser.name,
        version,
      }
    })
  }
  const detectOs = (oses, userAgent) => {
    return detect$4(oses, userAgent).map((os) => {
      const version = Version.detect(os.versionRegexes, userAgent)
      return {
        current: os.name,
        version,
      }
    })
  }

  const removeFromStart = (str, numChars) => {
    return str.substring(numChars)
  }

  const checkRange = (str, substr, start) =>
    substr === '' || (str.length >= substr.length && str.substr(start, start + substr.length) === substr)
  const removeLeading = (str, prefix) => {
    return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str
  }
  const contains$1 = (str, substr) => {
    return str.indexOf(substr) !== -1
  }
  const startsWith = (str, prefix) => {
    return checkRange(str, prefix, 0)
  }
  const endsWith = (str, suffix) => {
    return checkRange(str, suffix, str.length - suffix.length)
  }
  const blank = (r) => (s) => s.replace(r, '')
  const trim$3 = blank(/^\s+|\s+$/g)
  const lTrim = blank(/^\s+/g)
  const rTrim = blank(/\s+$/g)
  const isNotEmpty = (s) => s.length > 0
  const isEmpty$3 = (s) => !isNotEmpty(s)
  const repeat = (s, count) => (count <= 0 ? '' : new Array(count + 1).join(s))
  const toInt = (value, radix = 10) => {
    const num = parseInt(value, radix)
    return isNaN(num) ? Optional.none() : Optional.some(num)
  }

  const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/
  const checkContains = (target) => {
    return (uastring) => {
      return contains$1(uastring, target)
    }
  }
  const browsers = [
    {
      name: 'Edge',
      versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
      search: (uastring) => {
        return (
          contains$1(uastring, 'edge/') &&
          contains$1(uastring, 'chrome') &&
          contains$1(uastring, 'safari') &&
          contains$1(uastring, 'applewebkit')
        )
      },
    },
    {
      name: 'Chromium',
      brand: 'Chromium',
      versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
      search: (uastring) => {
        return contains$1(uastring, 'chrome') && !contains$1(uastring, 'chromeframe')
      },
    },
    {
      name: 'IE',
      versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
      search: (uastring) => {
        return contains$1(uastring, 'msie') || contains$1(uastring, 'trident')
      },
    },
    {
      name: 'Opera',
      versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
      search: checkContains('opera'),
    },
    {
      name: 'Firefox',
      versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
      search: checkContains('firefox'),
    },
    {
      name: 'Safari',
      versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
      search: (uastring) => {
        return (
          (contains$1(uastring, 'safari') || contains$1(uastring, 'mobile/')) && contains$1(uastring, 'applewebkit')
        )
      },
    },
  ]
  const oses = [
    {
      name: 'Windows',
      search: checkContains('win'),
      versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/],
    },
    {
      name: 'iOS',
      search: (uastring) => {
        return contains$1(uastring, 'iphone') || contains$1(uastring, 'ipad')
      },
      versionRegexes: [
        /.*?version\/\ ?([0-9]+)\.([0-9]+).*/,
        /.*cpu os ([0-9]+)_([0-9]+).*/,
        /.*cpu iphone os ([0-9]+)_([0-9]+).*/,
      ],
    },
    {
      name: 'Android',
      search: checkContains('android'),
      versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/],
    },
    {
      name: 'macOS',
      search: checkContains('mac os x'),
      versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/],
    },
    {
      name: 'Linux',
      search: checkContains('linux'),
      versionRegexes: [],
    },
    {
      name: 'Solaris',
      search: checkContains('sunos'),
      versionRegexes: [],
    },
    {
      name: 'FreeBSD',
      search: checkContains('freebsd'),
      versionRegexes: [],
    },
    {
      name: 'ChromeOS',
      search: checkContains('cros'),
      versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/],
    },
  ]
  const PlatformInfo = {
    browsers: constant(browsers),
    oses: constant(oses),
  }

  const edge = 'Edge'
  const chromium = 'Chromium'
  const ie = 'IE'
  const opera = 'Opera'
  const firefox = 'Firefox'
  const safari = 'Safari'
  const unknown$1 = () => {
    return nu$2({
      current: undefined,
      version: Version.unknown(),
    })
  }
  const nu$2 = (info) => {
    const current = info.current
    const version = info.version
    const isBrowser = (name) => () => current === name
    return {
      current,
      version,
      isEdge: isBrowser(edge),
      isChromium: isBrowser(chromium),
      isIE: isBrowser(ie),
      isOpera: isBrowser(opera),
      isFirefox: isBrowser(firefox),
      isSafari: isBrowser(safari),
    }
  }
  const Browser = {
    unknown: unknown$1,
    nu: nu$2,
    edge: constant(edge),
    chromium: constant(chromium),
    ie: constant(ie),
    opera: constant(opera),
    firefox: constant(firefox),
    safari: constant(safari),
  }

  const windows = 'Windows'
  const ios = 'iOS'
  const android = 'Android'
  const linux = 'Linux'
  const macos = 'macOS'
  const solaris = 'Solaris'
  const freebsd = 'FreeBSD'
  const chromeos = 'ChromeOS'
  const unknown = () => {
    return nu$1({
      current: undefined,
      version: Version.unknown(),
    })
  }
  const nu$1 = (info) => {
    const current = info.current
    const version = info.version
    const isOS = (name) => () => current === name
    return {
      current,
      version,
      isWindows: isOS(windows),
      isiOS: isOS(ios),
      isAndroid: isOS(android),
      isMacOS: isOS(macos),
      isLinux: isOS(linux),
      isSolaris: isOS(solaris),
      isFreeBSD: isOS(freebsd),
      isChromeOS: isOS(chromeos),
    }
  }
  const OperatingSystem = {
    unknown,
    nu: nu$1,
    windows: constant(windows),
    ios: constant(ios),
    android: constant(android),
    linux: constant(linux),
    macos: constant(macos),
    solaris: constant(solaris),
    freebsd: constant(freebsd),
    chromeos: constant(chromeos),
  }

  const detect$3 = (userAgent, userAgentDataOpt, mediaMatch) => {
    const browsers = PlatformInfo.browsers()
    const oses = PlatformInfo.oses()
    const browser = userAgentDataOpt
      .bind((userAgentData) => detectBrowser$1(browsers, userAgentData))
      .orThunk(() => detectBrowser(browsers, userAgent))
      .fold(Browser.unknown, Browser.nu)
    const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu)
    const deviceType = DeviceType(os, browser, userAgent, mediaMatch)
    return {
      browser,
      os,
      deviceType,
    }
  }
  const PlatformDetection = { detect: detect$3 }

  const mediaMatch = (query) => window.matchMedia(query).matches
  let platform$2 = cached(() =>
    PlatformDetection.detect(navigator.userAgent, Optional.from(navigator.userAgentData), mediaMatch)
  )
  const detect$2 = () => platform$2()

  const userAgent = navigator.userAgent
  const platform$1 = detect$2()
  const browser$1 = platform$1.browser
  const os = platform$1.os
  const deviceType = platform$1.deviceType
  const windowsPhone = userAgent.indexOf('Windows Phone') !== -1
  const Env = {
    transparentSrc: '',
    documentMode: browser$1.isIE() ? document.documentMode || 7 : 10,
    cacheSuffix: null,
    container: null,
    canHaveCSP: !browser$1.isIE(),
    windowsPhone,
    browser: {
      current: browser$1.current,
      version: browser$1.version,
      isChromium: browser$1.isChromium,
      isEdge: browser$1.isEdge,
      isFirefox: browser$1.isFirefox,
      isIE: browser$1.isIE,
      isOpera: browser$1.isOpera,
      isSafari: browser$1.isSafari,
    },
    os: {
      current: os.current,
      version: os.version,
      isAndroid: os.isAndroid,
      isChromeOS: os.isChromeOS,
      isFreeBSD: os.isFreeBSD,
      isiOS: os.isiOS,
      isLinux: os.isLinux,
      isMacOS: os.isMacOS,
      isSolaris: os.isSolaris,
      isWindows: os.isWindows,
    },
    deviceType: {
      isDesktop: deviceType.isDesktop,
      isiPad: deviceType.isiPad,
      isiPhone: deviceType.isiPhone,
      isPhone: deviceType.isPhone,
      isTablet: deviceType.isTablet,
      isTouch: deviceType.isTouch,
      isWebView: deviceType.isWebView,
    },
  }

  const whiteSpaceRegExp$1 = /^\s*|\s*$/g
  const trim$2 = (str) => {
    return str === null || str === undefined ? '' : ('' + str).replace(whiteSpaceRegExp$1, '')
  }
  const is$3 = (obj, type) => {
    if (!type) {
      return obj !== undefined
    }
    if (type === 'array' && isArray(obj)) {
      return true
    }
    return typeof obj === type
  }
  const makeMap$4 = (items, delim, map) => {
    let i
    items = items || []
    delim = delim || ','
    if (typeof items === 'string') {
      items = items.split(delim)
    }
    map = map || {}
    i = items.length
    while (i--) {
      map[items[i]] = {}
    }
    return map
  }
  const hasOwnProperty$1 = has$2
  const extend$3 = (obj, ...exts) => {
    for (let i = 0; i < exts.length; i++) {
      const ext = exts[i]
      for (const name in ext) {
        if (has$2(ext, name)) {
          const value = ext[name]
          if (value !== undefined) {
            obj[name] = value
          }
        }
      }
    }
    return obj
  }
  const walk$4 = function (o, f, n, s) {
    s = s || this
    if (o) {
      if (n) {
        o = o[n]
      }
      each$d(o, (o, i) => {
        if (f.call(s, o, i, n) === false) {
          return false
        }
        walk$4(o, f, n, s)
      })
    }
  }
  const resolve$2 = (n, o) => {
    let i, l
    o = o || window
    n = n.split('.')
    for (i = 0, l = n.length; i < l; i++) {
      o = o[n[i]]
      if (!o) {
        break
      }
    }
    return o
  }
  const explode$3 = (s, d) => {
    if (!s || is$3(s, 'array')) {
      return s
    }
    return map$1(s.split(d || ','), trim$2)
  }
  const _addCacheSuffix = (url) => {
    const cacheSuffix = Env.cacheSuffix
    if (cacheSuffix) {
      url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix
    }
    return url
  }
  const Tools = {
    trim: trim$2,
    isArray: isArray,
    is: is$3,
    toArray: toArray$1,
    makeMap: makeMap$4,
    each: each$d,
    map: map$1,
    grep: filter$4,
    inArray: indexOf,
    hasOwn: hasOwnProperty$1,
    extend: extend$3,
    walk: walk$4,
    resolve: resolve$2,
    explode: explode$3,
    _addCacheSuffix,
  }

  const is$2 = (lhs, rhs, comparator = tripleEquals) => lhs.exists((left) => comparator(left, rhs))
  const cat = (arr) => {
    const r = []
    const push = (x) => {
      r.push(x)
    }
    for (let i = 0; i < arr.length; i++) {
      arr[i].each(push)
    }
    return r
  }
  const lift2 = (oa, ob, f) =>
    oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none()
  const lift3 = (oa, ob, oc, f) =>
    oa.isSome() && ob.isSome() && oc.isSome()
      ? Optional.some(f(oa.getOrDie(), ob.getOrDie(), oc.getOrDie()))
      : Optional.none()
  const someIf = (b, a) => (b ? Optional.some(a) : Optional.none())

  typeof window !== 'undefined' ? window : Function('return this;')()

  const COMMENT = 8
  const DOCUMENT = 9
  const DOCUMENT_FRAGMENT = 11
  const ELEMENT = 1
  const TEXT = 3

  const name = (element) => {
    const r = element.dom.nodeName
    return r.toLowerCase()
  }
  const type$1 = (element) => element.dom.nodeType
  const isType = (t) => (element) => type$1(element) === t
  const isComment$1 = (element) => type$1(element) === COMMENT || name(element) === '#comment'
  const isElement$7 = isType(ELEMENT)
  const isText$a = isType(TEXT)
  const isDocument$2 = isType(DOCUMENT)
  const isDocumentFragment$1 = isType(DOCUMENT_FRAGMENT)
  const isTag = (tag) => (e) => isElement$7(e) && name(e) === tag

  const rawSet = (dom, key, value) => {
    if (isString(value) || isBoolean(value) || isNumber(value)) {
      dom.setAttribute(key, value + '')
    } else {
      console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom)
      throw new Error('Attribute value was not simple')
    }
  }
  const set$2 = (element, key, value) => {
    rawSet(element.dom, key, value)
  }
  const setAll$1 = (element, attrs) => {
    const dom = element.dom
    each$e(attrs, (v, k) => {
      rawSet(dom, k, v)
    })
  }
  const get$9 = (element, key) => {
    const v = element.dom.getAttribute(key)
    return v === null ? undefined : v
  }
  const getOpt = (element, key) => Optional.from(get$9(element, key))
  const has$1 = (element, key) => {
    const dom = element.dom
    return dom && dom.hasAttribute ? dom.hasAttribute(key) : false
  }
  const remove$b = (element, key) => {
    element.dom.removeAttribute(key)
  }
  const hasNone = (element) => {
    const attrs = element.dom.attributes
    return attrs === undefined || attrs === null || attrs.length === 0
  }
  const clone$4 = (element) =>
    foldl(
      element.dom.attributes,
      (acc, attr) => {
        acc[attr.name] = attr.value
        return acc
      },
      {}
    )

  const read$4 = (element, attr) => {
    const value = get$9(element, attr)
    return value === undefined || value === '' ? [] : value.split(' ')
  }
  const add$4 = (element, attr, id) => {
    const old = read$4(element, attr)
    const nu = old.concat([id])
    set$2(element, attr, nu.join(' '))
    return true
  }
  const remove$a = (element, attr, id) => {
    const nu = filter$6(read$4(element, attr), (v) => v !== id)
    if (nu.length > 0) {
      set$2(element, attr, nu.join(' '))
    } else {
      remove$b(element, attr)
    }
    return false
  }

  const supports = (element) => element.dom.classList !== undefined
  const get$8 = (element) => read$4(element, 'class')
  const add$3 = (element, clazz) => add$4(element, 'class', clazz)
  const remove$9 = (element, clazz) => remove$a(element, 'class', clazz)
  const toggle$2 = (element, clazz) => {
    if (contains$2(get$8(element), clazz)) {
      return remove$9(element, clazz)
    } else {
      return add$3(element, clazz)
    }
  }

  const add$2 = (element, clazz) => {
    if (supports(element)) {
      element.dom.classList.add(clazz)
    } else {
      add$3(element, clazz)
    }
  }
  const cleanClass = (element) => {
    const classList = supports(element) ? element.dom.classList : get$8(element)
    if (classList.length === 0) {
      remove$b(element, 'class')
    }
  }
  const remove$8 = (element, clazz) => {
    if (supports(element)) {
      const classList = element.dom.classList
      classList.remove(clazz)
    } else {
      remove$9(element, clazz)
    }
    cleanClass(element)
  }
  const toggle$1 = (element, clazz) => {
    const result = supports(element) ? element.dom.classList.toggle(clazz) : toggle$2(element, clazz)
    cleanClass(element)
    return result
  }
  const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz)

  const isSupported$1 = (dom) => dom.style !== undefined && isFunction(dom.style.getPropertyValue)

  const fromHtml$1 = (html, scope) => {
    const doc = scope || document
    const div = doc.createElement('div')
    div.innerHTML = html
    if (!div.hasChildNodes() || div.childNodes.length > 1) {
      const message = 'HTML does not have a single root node'
      console.error(message, html)
      throw new Error(message)
    }
    return fromDom$2(div.childNodes[0])
  }
  const fromTag = (tag, scope) => {
    const doc = scope || document
    const node = doc.createElement(tag)
    return fromDom$2(node)
  }
  const fromText = (text, scope) => {
    const doc = scope || document
    const node = doc.createTextNode(text)
    return fromDom$2(node)
  }
  const fromDom$2 = (node) => {
    if (node === null || node === undefined) {
      throw new Error('Node cannot be null or undefined')
    }
    return { dom: node }
  }
  const fromPoint$2 = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom$2)
  const SugarElement = {
    fromHtml: fromHtml$1,
    fromTag,
    fromText,
    fromDom: fromDom$2,
    fromPoint: fromPoint$2,
  }

  const toArray = (target, f) => {
    const r = []
    const recurse = (e) => {
      r.push(e)
      return f(e)
    }
    let cur = f(target)
    do {
      cur = cur.bind(recurse)
    } while (cur.isSome())
    return r
  }

  const is$1 = (element, selector) => {
    const dom = element.dom
    if (dom.nodeType !== ELEMENT) {
      return false
    } else {
      const elem = dom
      if (elem.matches !== undefined) {
        return elem.matches(selector)
      } else if (elem.msMatchesSelector !== undefined) {
        return elem.msMatchesSelector(selector)
      } else if (elem.webkitMatchesSelector !== undefined) {
        return elem.webkitMatchesSelector(selector)
      } else if (elem.mozMatchesSelector !== undefined) {
        return elem.mozMatchesSelector(selector)
      } else {
        throw new Error('Browser lacks native selectors')
      }
    }
  }
  const bypassSelector = (dom) =>
    (dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT) ||
    dom.childElementCount === 0
  const all = (selector, scope) => {
    const base = scope === undefined ? document : scope.dom
    return bypassSelector(base) ? [] : map$3(base.querySelectorAll(selector), SugarElement.fromDom)
  }
  const one = (selector, scope) => {
    const base = scope === undefined ? document : scope.dom
    return bypassSelector(base)
      ? Optional.none()
      : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom)
  }

  const eq = (e1, e2) => e1.dom === e2.dom
  const contains = (e1, e2) => {
    const d1 = e1.dom
    const d2 = e2.dom
    return d1 === d2 ? false : d1.contains(d2)
  }

  const owner$1 = (element) => SugarElement.fromDom(element.dom.ownerDocument)
  const documentOrOwner = (dos) => (isDocument$2(dos) ? dos : owner$1(dos))
  const documentElement = (element) => SugarElement.fromDom(documentOrOwner(element).dom.documentElement)
  const defaultView = (element) => SugarElement.fromDom(documentOrOwner(element).dom.defaultView)
  const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom)
  const parentElement = (element) => Optional.from(element.dom.parentElement).map(SugarElement.fromDom)
  const parents$1 = (element, isRoot) => {
    const stop = isFunction(isRoot) ? isRoot : never
    let dom = element.dom
    const ret = []
    while (dom.parentNode !== null && dom.parentNode !== undefined) {
      const rawParent = dom.parentNode
      const p = SugarElement.fromDom(rawParent)
      ret.push(p)
      if (stop(p) === true) {
        break
      } else {
        dom = rawParent
      }
    }
    return ret
  }
  const siblings = (element) => {
    const filterSelf = (elements) => filter$6(elements, (x) => !eq(element, x))
    return parent(element).map(children).map(filterSelf).getOr([])
  }
  const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom)
  const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom)
  const prevSiblings = (element) => reverse(toArray(element, prevSibling))
  const nextSiblings = (element) => toArray(element, nextSibling)
  const children = (element) => map$3(element.dom.childNodes, SugarElement.fromDom)
  const child$1 = (element, index) => {
    const cs = element.dom.childNodes
    return Optional.from(cs[index]).map(SugarElement.fromDom)
  }
  const firstChild = (element) => child$1(element, 0)
  const lastChild = (element) => child$1(element, element.dom.childNodes.length - 1)
  const childNodesCount = (element) => element.dom.childNodes.length

  const getHead = (doc) => {
    const b = doc.dom.head
    if (b === null || b === undefined) {
      throw new Error('Head is not available yet')
    }
    return SugarElement.fromDom(b)
  }

  const isShadowRoot = (dos) => isDocumentFragment$1(dos) && isNonNullable(dos.dom.host)
  const supported = isFunction(Element.prototype.attachShadow) && isFunction(Node.prototype.getRootNode)
  const isSupported = constant(supported)
  const getRootNode = supported ? (e) => SugarElement.fromDom(e.dom.getRootNode()) : documentOrOwner
  const getStyleContainer = (dos) => (isShadowRoot(dos) ? dos : getHead(documentOrOwner(dos)))
  const getShadowRoot = (e) => {
    const r = getRootNode(e)
    return isShadowRoot(r) ? Optional.some(r) : Optional.none()
  }
  const getShadowHost = (e) => SugarElement.fromDom(e.dom.host)
  const getOriginalEventTarget = (event) => {
    if (isSupported() && isNonNullable(event.target)) {
      const el = SugarElement.fromDom(event.target)
      if (isElement$7(el) && isOpenShadowHost(el)) {
        if (event.composed && event.composedPath) {
          const composedPath = event.composedPath()
          if (composedPath) {
            return head(composedPath)
          }
        }
      }
    }
    return Optional.from(event.target)
  }
  const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot)

  const inBody = (element) => {
    const dom = isText$a(element) ? element.dom.parentNode : element.dom
    if (dom === undefined || dom === null || dom.ownerDocument === null) {
      return false
    }
    const doc = dom.ownerDocument
    return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost))
  }

  const internalSet = (dom, property, value) => {
    if (!isString(value)) {
      console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom)
      throw new Error('CSS value must be a string: ' + value)
    }
    if (isSupported$1(dom)) {
      dom.style.setProperty(property, value)
    }
  }
  const internalRemove = (dom, property) => {
    if (isSupported$1(dom)) {
      dom.style.removeProperty(property)
    }
  }
  const set$1 = (element, property, value) => {
    const dom = element.dom
    internalSet(dom, property, value)
  }
  const setAll = (element, css) => {
    const dom = element.dom
    each$e(css, (v, k) => {
      internalSet(dom, k, v)
    })
  }
  const get$7 = (element, property) => {
    const dom = element.dom
    const styles = window.getComputedStyle(dom)
    const r = styles.getPropertyValue(property)
    return r === '' && !inBody(element) ? getUnsafeProperty(dom, property) : r
  }
  const getUnsafeProperty = (dom, property) => (isSupported$1(dom) ? dom.style.getPropertyValue(property) : '')
  const getRaw$1 = (element, property) => {
    const dom = element.dom
    const raw = getUnsafeProperty(dom, property)
    return Optional.from(raw).filter((r) => r.length > 0)
  }
  const getAllRaw = (element) => {
    const css = {}
    const dom = element.dom
    if (isSupported$1(dom)) {
      for (let i = 0; i < dom.style.length; i++) {
        const ruleName = dom.style.item(i)
        css[ruleName] = dom.style[ruleName]
      }
    }
    return css
  }
  const remove$7 = (element, property) => {
    const dom = element.dom
    internalRemove(dom, property)
    if (is$2(getOpt(element, 'style').map(trim$3), '')) {
      remove$b(element, 'style')
    }
  }
  const reflow = (e) => e.dom.offsetWidth

  const before$3 = (marker, element) => {
    const parent$1 = parent(marker)
    parent$1.each((v) => {
      v.dom.insertBefore(element.dom, marker.dom)
    })
  }
  const after$4 = (marker, element) => {
    const sibling = nextSibling(marker)
    sibling.fold(
      () => {
        const parent$1 = parent(marker)
        parent$1.each((v) => {
          append$1(v, element)
        })
      },
      (v) => {
        before$3(v, element)
      }
    )
  }
  const prepend = (parent, element) => {
    const firstChild$1 = firstChild(parent)
    firstChild$1.fold(
      () => {
        append$1(parent, element)
      },
      (v) => {
        parent.dom.insertBefore(element.dom, v.dom)
      }
    )
  }
  const append$1 = (parent, element) => {
    parent.dom.appendChild(element.dom)
  }
  const wrap$2 = (element, wrapper) => {
    before$3(element, wrapper)
    append$1(wrapper, element)
  }

  const after$3 = (marker, elements) => {
    each$f(elements, (x, i) => {
      const e = i === 0 ? marker : elements[i - 1]
      after$4(e, x)
    })
  }
  const append = (parent, elements) => {
    each$f(elements, (x) => {
      append$1(parent, x)
    })
  }

  const empty = (element) => {
    element.dom.textContent = ''
    each$f(children(element), (rogue) => {
      remove$6(rogue)
    })
  }
  const remove$6 = (element) => {
    const dom = element.dom
    if (dom.parentNode !== null) {
      dom.parentNode.removeChild(dom)
    }
  }
  const unwrap = (wrapper) => {
    const children$1 = children(wrapper)
    if (children$1.length > 0) {
      after$3(wrapper, children$1)
    }
    remove$6(wrapper)
  }

  const fromHtml = (html, scope) => {
    const doc = scope || document
    const div = doc.createElement('div')
    div.innerHTML = html
    return children(SugarElement.fromDom(div))
  }
  const fromDom$1 = (nodes) => map$3(nodes, SugarElement.fromDom)

  const get$6 = (element) => element.dom.innerHTML
  const set = (element, content) => {
    const owner = owner$1(element)
    const docDom = owner.dom
    const fragment = SugarElement.fromDom(docDom.createDocumentFragment())
    const contentElements = fromHtml(content, docDom)
    append(fragment, contentElements)
    empty(element)
    append$1(element, fragment)
  }
  const getOuter = (element) => {
    const container = SugarElement.fromTag('div')
    const clone = SugarElement.fromDom(element.dom.cloneNode(true))
    append$1(container, clone)
    return get$6(container)
  }

  const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
    target,
    x,
    y,
    stop,
    prevent,
    kill,
    raw,
  })
  const fromRawEvent = (rawEvent) => {
    const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target))
    const stop = () => rawEvent.stopPropagation()
    const prevent = () => rawEvent.preventDefault()
    const kill = compose(prevent, stop)
    return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent)
  }
  const handle$1 = (filter, handler) => (rawEvent) => {
    if (filter(rawEvent)) {
      handler(fromRawEvent(rawEvent))
    }
  }
  const binder = (element, event, filter, handler, useCapture) => {
    const wrapped = handle$1(filter, handler)
    element.dom.addEventListener(event, wrapped, useCapture)
    return { unbind: curry(unbind, element, event, wrapped, useCapture) }
  }
  const bind$2 = (element, event, filter, handler) => binder(element, event, filter, handler, false)
  const unbind = (element, event, handler, useCapture) => {
    element.dom.removeEventListener(event, handler, useCapture)
  }

  const r = (left, top) => {
    const translate = (x, y) => r(left + x, top + y)
    return {
      left,
      top,
      translate,
    }
  }
  const SugarPosition = r

  const boxPosition = (dom) => {
    const box = dom.getBoundingClientRect()
    return SugarPosition(box.left, box.top)
  }
  const firstDefinedOrZero = (a, b) => {
    if (a !== undefined) {
      return a
    } else {
      return b !== undefined ? b : 0
    }
  }
  const absolute = (element) => {
    const doc = element.dom.ownerDocument
    const body = doc.body
    const win = doc.defaultView
    const html = doc.documentElement
    if (body === element.dom) {
      return SugarPosition(body.offsetLeft, body.offsetTop)
    }
    const scrollTop = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageYOffset, html.scrollTop)
    const scrollLeft = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageXOffset, html.scrollLeft)
    const clientTop = firstDefinedOrZero(html.clientTop, body.clientTop)
    const clientLeft = firstDefinedOrZero(html.clientLeft, body.clientLeft)
    return viewport(element).translate(scrollLeft - clientLeft, scrollTop - clientTop)
  }
  const viewport = (element) => {
    const dom = element.dom
    const doc = dom.ownerDocument
    const body = doc.body
    if (body === dom) {
      return SugarPosition(body.offsetLeft, body.offsetTop)
    }
    if (!inBody(element)) {
      return SugarPosition(0, 0)
    }
    return boxPosition(dom)
  }

  const get$5 = (_DOC) => {
    const doc = _DOC !== undefined ? _DOC.dom : document
    const x = doc.body.scrollLeft || doc.documentElement.scrollLeft
    const y = doc.body.scrollTop || doc.documentElement.scrollTop
    return SugarPosition(x, y)
  }
  const to = (x, y, _DOC) => {
    const doc = _DOC !== undefined ? _DOC.dom : document
    const win = doc.defaultView
    if (win) {
      win.scrollTo(x, y)
    }
  }
  const intoView = (element, alignToTop) => {
    const isSafari = detect$2().browser.isSafari()
    if (isSafari && isFunction(element.dom.scrollIntoViewIfNeeded)) {
      element.dom.scrollIntoViewIfNeeded(false)
    } else {
      element.dom.scrollIntoView(alignToTop)
    }
  }

  const get$4 = (_win) => {
    const win = _win === undefined ? window : _win
    if (detect$2().browser.isFirefox()) {
      return Optional.none()
    } else {
      return Optional.from(win.visualViewport)
    }
  }
  const bounds = (x, y, width, height) => ({
    x,
    y,
    width,
    height,
    right: x + width,
    bottom: y + height,
  })
  const getBounds = (_win) => {
    const win = _win === undefined ? window : _win
    const doc = win.document
    const scroll = get$5(SugarElement.fromDom(doc))
    return get$4(win).fold(
      () => {
        const html = win.document.documentElement
        const width = html.clientWidth
        const height = html.clientHeight
        return bounds(scroll.left, scroll.top, width, height)
      },
      (visualViewport) =>
        bounds(
          Math.max(visualViewport.pageLeft, scroll.left),
          Math.max(visualViewport.pageTop, scroll.top),
          visualViewport.width,
          visualViewport.height
        )
    )
  }

  const isNodeType = (type) => {
    return (node) => {
      return !!node && node.nodeType === type
    }
  }
  const isRestrictedNode = (node) => !!node && !Object.getPrototypeOf(node)
  const isElement$6 = isNodeType(1)
  const matchNodeNames = (names) => {
    const lowercasedNames = names.map((s) => s.toLowerCase())
    return (node) => {
      if (node && node.nodeName) {
        const nodeName = node.nodeName.toLowerCase()
        return contains$2(lowercasedNames, nodeName)
      }
      return false
    }
  }
  const matchStyleValues = (name, values) => {
    const items = values.toLowerCase().split(' ')
    return (node) => {
      if (isElement$6(node)) {
        for (let i = 0; i < items.length; i++) {
          const computed = node.ownerDocument.defaultView.getComputedStyle(node, null)
          const cssValue = computed ? computed.getPropertyValue(name) : null
          if (cssValue === items[i]) {
            return true
          }
        }
      }
      return false
    }
  }
  const hasAttribute = (attrName) => {
    return (node) => {
      return isElement$6(node) && node.hasAttribute(attrName)
    }
  }
  const hasAttributeValue = (attrName, attrValue) => {
    return (node) => {
      return isElement$6(node) && node.getAttribute(attrName) === attrValue
    }
  }
  const isBogus$2 = (node) => isElement$6(node) && node.hasAttribute('data-mce-bogus')
  const isBogusAll$1 = (node) => isElement$6(node) && node.getAttribute('data-mce-bogus') === 'all'
  const isTable$3 = (node) => isElement$6(node) && node.tagName === 'TABLE'
  const hasContentEditableState = (value) => {
    return (node) => {
      if (isElement$6(node)) {
        if (node.contentEditable === value) {
          return true
        }
        if (node.getAttribute('data-mce-contenteditable') === value) {
          return true
        }
      }
      return false
    }
  }
  const isTextareaOrInput = matchNodeNames(['textarea', 'input'])
  const isText$9 = isNodeType(3)
  const isCData = isNodeType(4)
  const isPi = isNodeType(7)
  const isComment = isNodeType(8)
  const isDocument$1 = isNodeType(9)
  const isDocumentFragment = isNodeType(11)
  const isBr$6 = matchNodeNames(['br'])
  const isImg = matchNodeNames(['img'])
  const isContentEditableTrue$5 = hasContentEditableState('true')
  const isContentEditableFalse$b = hasContentEditableState('false')
  const isTableCell$5 = matchNodeNames(['td', 'th'])
  const isMedia$2 = matchNodeNames(['video', 'audio', 'object', 'embed'])

  const browser = detect$2().browser
  const firstElement = (nodes) => find$2(nodes, isElement$7)
  const getTableCaptionDeltaY = (elm) => {
    if (browser.isFirefox() && name(elm) === 'table') {
      return firstElement(children(elm))
        .filter((elm) => {
          return name(elm) === 'caption'
        })
        .bind((caption) => {
          return firstElement(nextSiblings(caption)).map((body) => {
            const bodyTop = body.dom.offsetTop
            const captionTop = caption.dom.offsetTop
            const captionHeight = caption.dom.offsetHeight
            return bodyTop <= captionTop ? -captionHeight : 0
          })
        })
        .getOr(0)
    } else {
      return 0
    }
  }
  const hasChild = (elm, child) => elm.children && contains$2(elm.children, child)
  const getPos = (body, elm, rootElm) => {
    let x = 0,
      y = 0
    const doc = body.ownerDocument
    rootElm = rootElm ? rootElm : body
    if (elm) {
      if (rootElm === body && elm.getBoundingClientRect && get$7(SugarElement.fromDom(body), 'position') === 'static') {
        const pos = elm.getBoundingClientRect()
        x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - doc.documentElement.clientLeft
        y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - doc.documentElement.clientTop
        return {
          x,
          y,
        }
      }
      let offsetParent = elm
      while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
        const castOffsetParent = offsetParent
        x += castOffsetParent.offsetLeft || 0
        y += castOffsetParent.offsetTop || 0
        offsetParent = castOffsetParent.offsetParent
      }
      offsetParent = elm.parentNode
      while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
        x -= offsetParent.scrollLeft || 0
        y -= offsetParent.scrollTop || 0
        offsetParent = offsetParent.parentNode
      }
      y += getTableCaptionDeltaY(SugarElement.fromDom(elm))
    }
    return {
      x,
      y,
    }
  }

  var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => {
    if (is(scope, a)) {
      return Optional.some(scope)
    } else if (isFunction(isRoot) && isRoot(scope)) {
      return Optional.none()
    } else {
      return ancestor(scope, a, isRoot)
    }
  }

  const ancestor$3 = (scope, predicate, isRoot) => {
    let element = scope.dom
    const stop = isFunction(isRoot) ? isRoot : never
    while (element.parentNode) {
      element = element.parentNode
      const el = SugarElement.fromDom(element)
      if (predicate(el)) {
        return Optional.some(el)
      } else if (stop(el)) {
        break
      }
    }
    return Optional.none()
  }
  const closest$4 = (scope, predicate, isRoot) => {
    const is = (s, test) => test(s)
    return ClosestOrAncestor(is, ancestor$3, scope, predicate, isRoot)
  }
  const sibling$1 = (scope, predicate) => {
    const element = scope.dom
    if (!element.parentNode) {
      return Optional.none()
    }
    return child(SugarElement.fromDom(element.parentNode), (x) => !eq(scope, x) && predicate(x))
  }
  const child = (scope, predicate) => {
    const pred = (node) => predicate(SugarElement.fromDom(node))
    const result = find$2(scope.dom.childNodes, pred)
    return result.map(SugarElement.fromDom)
  }
  const descendant$1 = (scope, predicate) => {
    const descend = (node) => {
      for (let i = 0; i < node.childNodes.length; i++) {
        const child = SugarElement.fromDom(node.childNodes[i])
        if (predicate(child)) {
          return Optional.some(child)
        }
        const res = descend(node.childNodes[i])
        if (res.isSome()) {
          return res
        }
      }
      return Optional.none()
    }
    return descend(scope.dom)
  }

  const ancestor$2 = (scope, selector, isRoot) => ancestor$3(scope, (e) => is$1(e, selector), isRoot)
  const descendant = (scope, selector) => one(selector, scope)
  const closest$3 = (scope, selector, isRoot) => {
    const is = (element, selector) => is$1(element, selector)
    return ClosestOrAncestor(is, ancestor$2, scope, selector, isRoot)
  }

  const StyleSheetLoader = (documentOrShadowRoot, settings = {}) => {
    let idCount = 0
    const loadedStates = {}
    const edos = SugarElement.fromDom(documentOrShadowRoot)
    const doc = documentOrOwner(edos)
    const maxLoadTime = settings.maxLoadTime || 5000
    const _setReferrerPolicy = (referrerPolicy) => {
      settings.referrerPolicy = referrerPolicy
    }
    const addStyle = (element) => {
      append$1(getStyleContainer(edos), element)
    }
    const removeStyle = (id) => {
      const styleContainer = getStyleContainer(edos)
      descendant(styleContainer, '#' + id).each(remove$6)
    }
    const getOrCreateState = (url) =>
      get$a(loadedStates, url).getOrThunk(() => ({
        id: 'mce-u' + idCount++,
        passed: [],
        failed: [],
        count: 0,
      }))
    const load = (url) =>
      new Promise((success, failure) => {
        let link
        const urlWithSuffix = Tools._addCacheSuffix(url)
        const state = getOrCreateState(urlWithSuffix)
        loadedStates[urlWithSuffix] = state
        state.count++
        const resolve = (callbacks, status) => {
          each$f(callbacks, call)
          state.status = status
          state.passed = []
          state.failed = []
          if (link) {
            link.onload = null
            link.onerror = null
            link = null
          }
        }
        const passed = () => resolve(state.passed, 2)
        const failed = () => resolve(state.failed, 3)
        const wait = (testCallback, waitCallback) => {
          if (!testCallback()) {
            if (Date.now() - startTime < maxLoadTime) {
              setTimeout(waitCallback)
            } else {
              failed()
            }
          }
        }
        const waitForWebKitLinkLoaded = () => {
          wait(() => {
            const styleSheets = documentOrShadowRoot.styleSheets
            let i = styleSheets.length
            while (i--) {
              const styleSheet = styleSheets[i]
              const owner = styleSheet.ownerNode
              if (owner && owner.id === link.id) {
                passed()
                return true
              }
            }
            return false
          }, waitForWebKitLinkLoaded)
        }
        if (success) {
          state.passed.push(success)
        }
        if (failure) {
          state.failed.push(failure)
        }
        if (state.status === 1) {
          return
        }
        if (state.status === 2) {
          passed()
          return
        }
        if (state.status === 3) {
          failed()
          return
        }
        state.status = 1
        const linkElem = SugarElement.fromTag('link', doc.dom)
        setAll$1(linkElem, {
          rel: 'stylesheet',
          type: 'text/css',
          id: state.id,
        })
        const startTime = Date.now()
        if (settings.contentCssCors) {
          set$2(linkElem, 'crossOrigin', 'anonymous')
        }
        if (settings.referrerPolicy) {
          set$2(linkElem, 'referrerpolicy', settings.referrerPolicy)
        }
        link = linkElem.dom
        link.onload = waitForWebKitLinkLoaded
        link.onerror = failed
        addStyle(linkElem)
        set$2(linkElem, 'href', urlWithSuffix)
      })
    const loadAll = (urls) => {
      const loadedUrls = Promise.allSettled(map$3(urls, (url) => load(url).then(constant(url))))
      return loadedUrls.then((results) => {
        const parts = partition$2(results, (r) => r.status === 'fulfilled')
        if (parts.fail.length > 0) {
          return Promise.reject(map$3(parts.fail, (result) => result.reason))
        } else {
          return map$3(parts.pass, (result) => result.value)
        }
      })
    }
    const unload = (url) => {
      const urlWithSuffix = Tools._addCacheSuffix(url)
      get$a(loadedStates, urlWithSuffix).each((state) => {
        const count = --state.count
        if (count === 0) {
          delete loadedStates[urlWithSuffix]
          removeStyle(state.id)
        }
      })
    }
    const unloadAll = (urls) => {
      each$f(urls, (url) => {
        unload(url)
      })
    }
    return {
      load,
      loadAll,
      unload,
      unloadAll,
      _setReferrerPolicy,
    }
  }

  const create$d = () => {
    const map = new WeakMap()
    const forElement = (referenceElement, settings) => {
      const root = getRootNode(referenceElement)
      const rootDom = root.dom
      return Optional.from(map.get(rootDom)).getOrThunk(() => {
        const sl = StyleSheetLoader(rootDom, settings)
        map.set(rootDom, sl)
        return sl
      })
    }
    return { forElement }
  }
  const instance = create$d()

  class DomTreeWalker {
    constructor(startNode, rootNode) {
      this.node = startNode
      this.rootNode = rootNode
      this.current = this.current.bind(this)
      this.next = this.next.bind(this)
      this.prev = this.prev.bind(this)
      this.prev2 = this.prev2.bind(this)
    }
    current() {
      return this.node
    }
    next(shallow) {
      this.node = this.findSibling(this.node, 'firstChild', 'nextSibling', shallow)
      return this.node
    }
    prev(shallow) {
      this.node = this.findSibling(this.node, 'lastChild', 'previousSibling', shallow)
      return this.node
    }
    prev2(shallow) {
      this.node = this.findPreviousNode(this.node, shallow)
      return this.node
    }
    findSibling(node, startName, siblingName, shallow) {
      let sibling, parent
      if (node) {
        if (!shallow && node[startName]) {
          return node[startName]
        }
        if (node !== this.rootNode) {
          sibling = node[siblingName]
          if (sibling) {
            return sibling
          }
          for (parent = node.parentNode; parent && parent !== this.rootNode; parent = parent.parentNode) {
            sibling = parent[siblingName]
            if (sibling) {
              return sibling
            }
          }
        }
      }
    }
    findPreviousNode(node, shallow) {
      let sibling, parent, child
      if (node) {
        sibling = node.previousSibling
        if (this.rootNode && sibling === this.rootNode) {
          return
        }
        if (sibling) {
          if (!shallow) {
            for (child = sibling.lastChild; child; child = child.lastChild) {
              if (!child.lastChild) {
                return child
              }
            }
          }
          return sibling
        }
        parent = node.parentNode
        if (parent && parent !== this.rootNode) {
          return parent
        }
      }
    }
  }

  const blocks = [
    'article',
    'aside',
    'details',
    'div',
    'dt',
    'figcaption',
    'footer',
    'form',
    'fieldset',
    'header',
    'hgroup',
    'html',
    'main',
    'nav',
    'section',
    'summary',
    'body',
    'p',
    'dl',
    'multicol',
    'dd',
    'figure',
    'address',
    'center',
    'blockquote',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'listing',
    'xmp',
    'pre',
    'plaintext',
    'menu',
    'dir',
    'ul',
    'ol',
    'li',
    'hr',
    'table',
    'tbody',
    'thead',
    'tfoot',
    'th',
    'tr',
    'td',
    'caption',
  ]
  const tableCells = ['td', 'th']
  const tableSections = ['thead', 'tbody', 'tfoot']
  const textBlocks = [
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'p',
    'div',
    'address',
    'pre',
    'form',
    'blockquote',
    'center',
    'dir',
    'fieldset',
    'header',
    'footer',
    'article',
    'section',
    'hgroup',
    'aside',
    'nav',
    'figure',
  ]
  const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
  const listItems$1 = ['li', 'dd', 'dt']
  const lists = ['ul', 'ol', 'dl']
  const wsElements = ['pre', 'script', 'textarea', 'style']
  const wrapBlockElements = ['pre'].concat(headings)
  const lazyLookup = (items) => {
    let lookup
    return (node) => {
      lookup = lookup ? lookup : mapToObject(items, always)
      return has$2(lookup, name(node))
    }
  }
  const isBlock$2 = lazyLookup(blocks)
  const isTable$2 = (node) => name(node) === 'table'
  const isInline$1 = (node) => isElement$7(node) && !isBlock$2(node)
  const isBr$5 = (node) => isElement$7(node) && name(node) === 'br'
  const isTextBlock$2 = lazyLookup(textBlocks)
  const isList = lazyLookup(lists)
  const isListItem = lazyLookup(listItems$1)
  const isTableSection = lazyLookup(tableSections)
  const isTableCell$4 = lazyLookup(tableCells)
  const isWsPreserveElement = lazyLookup(wsElements)
  const isWrapBlockElement = lazyLookup(wrapBlockElements)
  const isWrapElement = (node) => isWrapBlockElement(node) || isInline$1(node)

  const ancestor$1 = (scope, selector, isRoot) => ancestor$2(scope, selector, isRoot).isSome()

  const zeroWidth = '\uFEFF'
  const nbsp = '\xA0'
  const isZwsp$1 = (char) => char === zeroWidth
  const removeZwsp = (s) => s.replace(/\uFEFF/g, '')

  const descendants$1 = (scope, predicate) => {
    let result = []
    each$f(children(scope), (x) => {
      if (predicate(x)) {
        result = result.concat([x])
      }
      result = result.concat(descendants$1(x, predicate))
    })
    return result
  }

  const descendants = (scope, selector) => all(selector, scope)

  const NodeValue = (is, name) => {
    const get = (element) => {
      if (!is(element)) {
        throw new Error('Can only get ' + name + ' value of a ' + name + ' node')
      }
      return getOption(element).getOr('')
    }
    const getOption = (element) => (is(element) ? Optional.from(element.dom.nodeValue) : Optional.none())
    const set = (element, value) => {
      if (!is(element)) {
        throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node')
      }
      element.dom.nodeValue = value
    }
    return {
      get,
      getOption,
      set,
    }
  }

  const api$1 = NodeValue(isText$a, 'text')
  const get$3 = (element) => api$1.get(element)
  const getOption = (element) => api$1.getOption(element)

  const getLastChildren$1 = (elm) => {
    const children = []
    let rawNode = elm.dom
    while (rawNode) {
      children.push(SugarElement.fromDom(rawNode))
      rawNode = rawNode.lastChild
    }
    return children
  }
  const removeTrailingBr = (elm) => {
    const allBrs = descendants(elm, 'br')
    const brs = filter$6(getLastChildren$1(elm).slice(-1), isBr$5)
    if (allBrs.length === brs.length) {
      each$f(brs, remove$6)
    }
  }
  const createPaddingBr = () => {
    const br = SugarElement.fromTag('br')
    set$2(br, 'data-mce-bogus', '1')
    return br
  }
  const fillWithPaddingBr = (elm) => {
    empty(elm)
    append$1(elm, createPaddingBr())
  }
  const trimBlockTrailingBr = (elm) => {
    lastChild(elm).each((lastChild) => {
      prevSibling(lastChild).each((lastChildPrevSibling) => {
        if (isBlock$2(elm) && isBr$5(lastChild) && isBlock$2(lastChildPrevSibling)) {
          remove$6(lastChild)
        }
      })
    })
  }

  const ZWSP$1 = zeroWidth
  const isZwsp = isZwsp$1
  const trim$1 = removeZwsp

  const isElement$5 = isElement$6
  const isText$8 = isText$9
  const isCaretContainerBlock$1 = (node) => {
    if (isText$8(node)) {
      node = node.parentNode
    }
    return isElement$5(node) && node.hasAttribute('data-mce-caret')
  }
  const isCaretContainerInline = (node) => isText$8(node) && isZwsp(node.data)
  const isCaretContainer$2 = (node) => isCaretContainerBlock$1(node) || isCaretContainerInline(node)
  const hasContent = (node) => node.firstChild !== node.lastChild || !isBr$6(node.firstChild)
  const insertInline$1 = (node, before) => {
    const doc = node.ownerDocument
    const textNode = doc.createTextNode(ZWSP$1)
    const parentNode = node.parentNode
    if (!before) {
      const sibling = node.nextSibling
      if (isText$8(sibling)) {
        if (isCaretContainer$2(sibling)) {
          return sibling
        }
        if (startsWithCaretContainer$1(sibling)) {
          sibling.splitText(1)
          return sibling
        }
      }
      if (node.nextSibling) {
        parentNode.insertBefore(textNode, node.nextSibling)
      } else {
        parentNode.appendChild(textNode)
      }
    } else {
      const sibling = node.previousSibling
      if (isText$8(sibling)) {
        if (isCaretContainer$2(sibling)) {
          return sibling
        }
        if (endsWithCaretContainer$1(sibling)) {
          return sibling.splitText(sibling.data.length - 1)
        }
      }
      parentNode.insertBefore(textNode, node)
    }
    return textNode
  }
  const isBeforeInline = (pos) => {
    const container = pos.container()
    if (!isText$9(container)) {
      return false
    }
    return (
      container.data.charAt(pos.offset()) === ZWSP$1 ||
      (pos.isAtStart() && isCaretContainerInline(container.previousSibling))
    )
  }
  const isAfterInline = (pos) => {
    const container = pos.container()
    if (!isText$9(container)) {
      return false
    }
    return (
      container.data.charAt(pos.offset() - 1) === ZWSP$1 ||
      (pos.isAtEnd() && isCaretContainerInline(container.nextSibling))
    )
  }
  const insertBlock = (blockName, node, before) => {
    const doc = node.ownerDocument
    const blockNode = doc.createElement(blockName)
    blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after')
    blockNode.setAttribute('data-mce-bogus', 'all')
    blockNode.appendChild(createPaddingBr().dom)
    const parentNode = node.parentNode
    if (!before) {
      if (node.nextSibling) {
        parentNode.insertBefore(blockNode, node.nextSibling)
      } else {
        parentNode.appendChild(blockNode)
      }
    } else {
      parentNode.insertBefore(blockNode, node)
    }
    return blockNode
  }
  const startsWithCaretContainer$1 = (node) => isText$8(node) && node.data[0] === ZWSP$1
  const endsWithCaretContainer$1 = (node) => isText$8(node) && node.data[node.data.length - 1] === ZWSP$1
  const trimBogusBr = (elm) => {
    const brs = elm.getElementsByTagName('br')
    const lastBr = brs[brs.length - 1]
    if (isBogus$2(lastBr)) {
      lastBr.parentNode.removeChild(lastBr)
    }
  }
  const showCaretContainerBlock = (caretContainer) => {
    if (caretContainer && caretContainer.hasAttribute('data-mce-caret')) {
      trimBogusBr(caretContainer)
      caretContainer.removeAttribute('data-mce-caret')
      caretContainer.removeAttribute('data-mce-bogus')
      caretContainer.removeAttribute('style')
      caretContainer.removeAttribute('data-mce-style')
      caretContainer.removeAttribute('_moz_abspos')
      return caretContainer
    }
    return null
  }
  const isRangeInCaretContainerBlock = (range) => isCaretContainerBlock$1(range.startContainer)

  const isContentEditableTrue$4 = isContentEditableTrue$5
  const isContentEditableFalse$a = isContentEditableFalse$b
  const isBr$4 = isBr$6
  const isText$7 = isText$9
  const isInvalidTextElement = matchNodeNames(['script', 'style', 'textarea'])
  const isAtomicInline = matchNodeNames([
    'img',
    'input',
    'textarea',
    'hr',
    'iframe',
    'video',
    'audio',
    'object',
    'embed',
  ])
  const isTable$1 = matchNodeNames(['table'])
  const isCaretContainer$1 = isCaretContainer$2
  const isCaretCandidate$3 = (node) => {
    if (isCaretContainer$1(node)) {
      return false
    }
    if (isText$7(node)) {
      return !isInvalidTextElement(node.parentNode)
    }
    return isAtomicInline(node) || isBr$4(node) || isTable$1(node) || isNonUiContentEditableFalse(node)
  }
  const isUnselectable = (node) => isElement$6(node) && node.getAttribute('unselectable') === 'true'
  const isNonUiContentEditableFalse = (node) => isUnselectable(node) === false && isContentEditableFalse$a(node)
  const isInEditable = (node, root) => {
    for (node = node.parentNode; node && node !== root; node = node.parentNode) {
      if (isNonUiContentEditableFalse(node)) {
        return false
      }
      if (isContentEditableTrue$4(node)) {
        return true
      }
    }
    return true
  }
  const isAtomicContentEditableFalse = (node) => {
    if (!isNonUiContentEditableFalse(node)) {
      return false
    }
    return (
      foldl(
        from(node.getElementsByTagName('*')),
        (result, elm) => {
          return result || isContentEditableTrue$4(elm)
        },
        false
      ) !== true
    )
  }
  const isAtomic$1 = (node) => isAtomicInline(node) || isAtomicContentEditableFalse(node)
  const isEditableCaretCandidate$1 = (node, root) => isCaretCandidate$3(node) && isInEditable(node, root)

  const whiteSpaceRegExp = /^[ \t\r\n]*$/
  const isWhitespaceText = (text) => whiteSpaceRegExp.test(text)
  const isCollapsibleWhitespace$1 = (c) => ' \f\t\x0B'.indexOf(c) !== -1
  const isNewLineChar = (c) => c === '\n' || c === '\r'
  const isNewline = (text, idx) => (idx < text.length && idx >= 0 ? isNewLineChar(text[idx]) : false)
  const normalize$4 = (text, tabSpaces = 4, isStartOfContent = true, isEndOfContent = true) => {
    const tabSpace = repeat(' ', tabSpaces)
    const normalizedText = text.replace(/\t/g, tabSpace)
    const result = foldl(
      normalizedText,
      (acc, c) => {
        if (isCollapsibleWhitespace$1(c) || c === nbsp) {
          if (
            acc.pcIsSpace ||
            (acc.str === '' && isStartOfContent) ||
            (acc.str.length === normalizedText.length - 1 && isEndOfContent) ||
            isNewline(normalizedText, acc.str.length + 1)
          ) {
            return {
              pcIsSpace: false,
              str: acc.str + nbsp,
            }
          } else {
            return {
              pcIsSpace: true,
              str: acc.str + ' ',
            }
          }
        } else {
          return {
            pcIsSpace: isNewLineChar(c),
            str: acc.str + c,
          }
        }
      },
      {
        pcIsSpace: false,
        str: '',
      }
    )
    return result.str
  }

  const hasWhitespacePreserveParent = (node, rootNode) => {
    const rootElement = SugarElement.fromDom(rootNode)
    const startNode = SugarElement.fromDom(node)
    return ancestor$1(startNode, 'pre,code', curry(eq, rootElement))
  }
  const isWhitespace$1 = (node, rootNode) => {
    return isText$9(node) && isWhitespaceText(node.data) && hasWhitespacePreserveParent(node, rootNode) === false
  }
  const isNamedAnchor = (node) => {
    return (
      isElement$6(node) &&
      node.nodeName === 'A' &&
      !node.hasAttribute('href') &&
      (node.hasAttribute('name') || node.hasAttribute('id'))
    )
  }
  const isContent$1 = (node, rootNode) => {
    return (
      (isCaretCandidate$3(node) && isWhitespace$1(node, rootNode) === false) || isNamedAnchor(node) || isBookmark(node)
    )
  }
  const isBookmark = hasAttribute('data-mce-bookmark')
  const isBogus$1 = hasAttribute('data-mce-bogus')
  const isBogusAll = hasAttributeValue('data-mce-bogus', 'all')
  const isEmptyNode = (targetNode, skipBogus) => {
    let brCount = 0
    if (isContent$1(targetNode, targetNode)) {
      return false
    } else {
      let node = targetNode.firstChild
      if (!node) {
        return true
      }
      const walker = new DomTreeWalker(node, targetNode)
      do {
        if (skipBogus) {
          if (isBogusAll(node)) {
            node = walker.next(true)
            continue
          }
          if (isBogus$1(node)) {
            node = walker.next()
            continue
          }
        }
        if (isBr$6(node)) {
          brCount++
          node = walker.next()
          continue
        }
        if (isContent$1(node, targetNode)) {
          return false
        }
        node = walker.next()
      } while (node)
      return brCount <= 1
    }
  }
  const isEmpty$2 = (elm, skipBogus = true) => isEmptyNode(elm.dom, skipBogus)

  const isSpan = (node) => node.nodeName.toLowerCase() === 'span'
  const isInlineContent = (node, root) =>
    isNonNullable(node) && (isContent$1(node, root) || isInline$1(SugarElement.fromDom(node)))
  const surroundedByInlineContent = (node, root) => {
    const prev = new DomTreeWalker(node, root).prev(false)
    const next = new DomTreeWalker(node, root).next(false)
    const prevIsInline = isUndefined(prev) || isInlineContent(prev, root)
    const nextIsInline = isUndefined(next) || isInlineContent(next, root)
    return prevIsInline && nextIsInline
  }
  const isBookmarkNode$2 = (node) => isSpan(node) && node.getAttribute('data-mce-type') === 'bookmark'
  const isKeepTextNode = (node, root) => isText$9(node) && node.data.length > 0 && surroundedByInlineContent(node, root)
  const isKeepElement = (node) => (isElement$6(node) ? node.childNodes.length > 0 : false)
  const isDocument = (node) => isDocumentFragment(node) || isDocument$1(node)
  const trimNode = (dom, node, root) => {
    const rootNode = root || node
    if (isElement$6(node) && isBookmarkNode$2(node)) {
      return node
    }
    const children = node.childNodes
    for (let i = children.length - 1; i >= 0; i--) {
      trimNode(dom, children[i], rootNode)
    }
    if (isElement$6(node)) {
      const currentChildren = node.childNodes
      if (currentChildren.length === 1 && isBookmarkNode$2(currentChildren[0])) {
        node.parentNode.insertBefore(currentChildren[0], node)
      }
    }
    if (!isDocument(node) && !isContent$1(node, rootNode) && !isKeepElement(node) && !isKeepTextNode(node, rootNode)) {
      dom.remove(node)
    }
    return node
  }

  const makeMap$3 = Tools.makeMap
  const attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g
  const textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g
  const rawCharsRegExp = /[<>&\"\']/g
  const entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi
  const asciiMap = {
    128: '\u20AC',
    130: '\u201A',
    131: '\u0192',
    132: '\u201E',
    133: '\u2026',
    134: '\u2020',
    135: '\u2021',
    136: '\u02c6',
    137: '\u2030',
    138: '\u0160',
    139: '\u2039',
    140: '\u0152',
    142: '\u017d',
    145: '\u2018',
    146: '\u2019',
    147: '\u201C',
    148: '\u201D',
    149: '\u2022',
    150: '\u2013',
    151: '\u2014',
    152: '\u02DC',
    153: '\u2122',
    154: '\u0161',
    155: '\u203A',
    156: '\u0153',
    158: '\u017e',
    159: '\u0178',
  }
  const baseEntities = {
    '"': '&quot;',
    "'": '&#39;',
    '<': '&lt;',
    '>': '&gt;',
    '&': '&amp;',
    '`': '&#96;',
  }
  const reverseEntities = {
    '&lt;': '<',
    '&gt;': '>',
    '&amp;': '&',
    '&quot;': '"',
    '&apos;': `'`,
  }
  const nativeDecode = (text) => {
    const elm = SugarElement.fromTag('div').dom
    elm.innerHTML = text
    return elm.textContent || elm.innerText || text
  }
  const buildEntitiesLookup = (items, radix) => {
    let i, chr, entity
    const lookup = {}
    if (items) {
      items = items.split(',')
      radix = radix || 10
      for (i = 0; i < items.length; i += 2) {
        chr = String.fromCharCode(parseInt(items[i], radix))
        if (!baseEntities[chr]) {
          entity = '&' + items[i + 1] + ';'
          lookup[chr] = entity
          lookup[entity] = chr
        }
      }
      return lookup
    }
  }
  const namedEntities = buildEntitiesLookup(
    '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
      '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
      '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
      '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
      '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
      '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
      '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
      '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
      '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
      '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
      'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
      'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
      't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
      'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
      'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
      '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
      '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
      '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
      '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
      '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
      'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
      'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
      'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
      '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
      '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro',
    32
  )
  const encodeRaw = (text, attr) =>
    text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
      return baseEntities[chr] || chr
    })
  const encodeAllRaw = (text) =>
    ('' + text).replace(rawCharsRegExp, (chr) => {
      return baseEntities[chr] || chr
    })
  const encodeNumeric = (text, attr) =>
    text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
      if (chr.length > 1) {
        return '&#' + ((chr.charCodeAt(0) - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + ';'
      }
      return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'
    })
  const encodeNamed = (text, attr, entities) => {
    entities = entities || namedEntities
    return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
      return baseEntities[chr] || entities[chr] || chr
    })
  }
  const getEncodeFunc = (name, entities) => {
    const entitiesMap = buildEntitiesLookup(entities) || namedEntities
    const encodeNamedAndNumeric = (text, attr) =>
      text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
        if (baseEntities[chr] !== undefined) {
          return baseEntities[chr]
        }
        if (entitiesMap[chr] !== undefined) {
          return entitiesMap[chr]
        }
        if (chr.length > 1) {
          return '&#' + ((chr.charCodeAt(0) - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + ';'
        }
        return '&#' + chr.charCodeAt(0) + ';'
      })
    const encodeCustomNamed = (text, attr) => {
      return encodeNamed(text, attr, entitiesMap)
    }
    const nameMap = makeMap$3(name.replace(/\+/g, ','))
    if (nameMap.named && nameMap.numeric) {
      return encodeNamedAndNumeric
    }
    if (nameMap.named) {
      if (entities) {
        return encodeCustomNamed
      }
      return encodeNamed
    }
    if (nameMap.numeric) {
      return encodeNumeric
    }
    return encodeRaw
  }
  const decode = (text) =>
    text.replace(entityRegExp, (all, numeric) => {
      if (numeric) {
        if (numeric.charAt(0).toLowerCase() === 'x') {
          numeric = parseInt(numeric.substr(1), 16)
        } else {
          numeric = parseInt(numeric, 10)
        }
        if (numeric > 65535) {
          numeric -= 65536
          return String.fromCharCode(55296 + (numeric >> 10), 56320 + (numeric & 1023))
        }
        return asciiMap[numeric] || String.fromCharCode(numeric)
      }
      return reverseEntities[all] || namedEntities[all] || nativeDecode(all)
    })
  const Entities = {
    encodeRaw,
    encodeAllRaw,
    encodeNumeric,
    encodeNamed,
    getEncodeFunc,
    decode,
  }

  const mapCache = {},
    dummyObj = {}
  const makeMap$2 = Tools.makeMap,
    each$c = Tools.each,
    extend$2 = Tools.extend,
    explode$2 = Tools.explode,
    inArray = Tools.inArray
  const split$1 = (items, delim) => {
    items = Tools.trim(items)
    return items ? items.split(delim || ' ') : []
  }
  const createMap = (defaultValue, extendWith) => {
    const value = makeMap$2(defaultValue, ' ', makeMap$2(defaultValue.toUpperCase(), ' '))
    return extend$2(value, extendWith)
  }
  const getTextRootBlockElements = (schema) =>
    createMap('td th li dt dd figcaption caption details summary', schema.getTextBlockElements())
  const compileSchema = (type) => {
    const schema = {}
    let globalAttributes, blockContent
    let phrasingContent, flowContent, html4BlockContent, html4PhrasingContent
    const add = (name, attributes = '', children = '') => {
      const childNames = split$1(children)
      const names = split$1(name)
      let ni = names.length
      while (ni--) {
        const attributesOrder = split$1([globalAttributes, attributes].join(' '))
        schema[names[ni]] = {
          attributes: mapToObject(attributesOrder, () => ({})),
          attributesOrder,
          children: mapToObject(childNames, constant(dummyObj)),
        }
      }
    }
    const addAttrs = (name, attributes) => {
      const names = split$1(name)
      const attrs = split$1(attributes)
      let ni = names.length
      while (ni--) {
        const schemaItem = schema[names[ni]]
        for (let i = 0, l = attrs.length; i < l; i++) {
          schemaItem.attributes[attrs[i]] = {}
          schemaItem.attributesOrder.push(attrs[i])
        }
      }
    }
    if (mapCache[type]) {
      return mapCache[type]
    }
    globalAttributes = 'id accesskey class dir lang style tabindex title role'
    blockContent = 'address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul'
    phrasingContent =
      'a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd ' +
      'label map noscript object q s samp script select small span strong sub sup ' +
      'textarea u var #text #comment'
    if (type !== 'html4') {
      globalAttributes += ' contenteditable contextmenu draggable dropzone ' + 'hidden spellcheck translate'
      blockContent += ' article aside details dialog figure main header footer hgroup section nav'
      phrasingContent +=
        ' audio canvas command datalist mark meter output picture ' + 'progress time wbr video ruby bdi keygen'
    }
    if (type !== 'html5-strict') {
      globalAttributes += ' xml:lang'
      html4PhrasingContent = 'acronym applet basefont big font strike tt'
      phrasingContent = [phrasingContent, html4PhrasingContent].join(' ')
      each$c(split$1(html4PhrasingContent), (name) => {
        add(name, '', phrasingContent)
      })
      html4BlockContent = 'center dir isindex noframes'
      blockContent = [blockContent, html4BlockContent].join(' ')
      flowContent = [blockContent, phrasingContent].join(' ')
      each$c(split$1(html4BlockContent), (name) => {
        add(name, '', flowContent)
      })
    }
    flowContent = flowContent || [blockContent, phrasingContent].join(' ')
    add('html', 'manifest', 'head body')
    add('head', '', 'base command link meta noscript script style title')
    add('title hr noscript br')
    add('base', 'href target')
    add('link', 'href rel media hreflang type sizes hreflang')
    add('meta', 'name http-equiv content charset')
    add('style', 'media type scoped')
    add('script', 'src async defer type charset')
    add(
      'body',
      'onafterprint onbeforeprint onbeforeunload onblur onerror onfocus ' +
        'onhashchange onload onmessage onoffline ononline onpagehide onpageshow ' +
        'onpopstate onresize onscroll onstorage onunload',
      flowContent
    )
    add('address dt dd div caption', '', flowContent)
    add(
      'h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn',
      '',
      phrasingContent
    )
    add('blockquote', 'cite', flowContent)
    add('ol', 'reversed start type', 'li')
    add('ul', '', 'li')
    add('li', 'value', flowContent)
    add('dl', '', 'dt dd')
    add('a', 'href target rel media hreflang type', phrasingContent)
    add('q', 'cite', phrasingContent)
    add('ins del', 'cite datetime', flowContent)
    add('img', 'src sizes srcset alt usemap ismap width height')
    add('iframe', 'src name width height', flowContent)
    add('embed', 'src type width height')
    add('object', 'data type typemustmatch name usemap form width height', [flowContent, 'param'].join(' '))
    add('param', 'name value')
    add('map', 'name', [flowContent, 'area'].join(' '))
    add('area', 'alt coords shape href target rel media hreflang type')
    add('table', 'border', 'caption colgroup thead tfoot tbody tr' + (type === 'html4' ? ' col' : ''))
    add('colgroup', 'span', 'col')
    add('col', 'span')
    add('tbody thead tfoot', '', 'tr')
    add('tr', '', 'td th')
    add('td', 'colspan rowspan headers', flowContent)
    add('th', 'colspan rowspan headers scope abbr', flowContent)
    add('form', 'accept-charset action autocomplete enctype method name novalidate target', flowContent)
    add('fieldset', 'disabled form name', [flowContent, 'legend'].join(' '))
    add('label', 'form for', phrasingContent)
    add(
      'input',
      'accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate ' +
        'formtarget height list max maxlength min multiple name pattern readonly required size src step type value width'
    )
    add(
      'button',
      'disabled form formaction formenctype formmethod formnovalidate formtarget name type value',
      type === 'html4' ? flowContent : phrasingContent
    )
    add('select', 'disabled form multiple name required size', 'option optgroup')
    add('optgroup', 'disabled label', 'option')
    add('option', 'disabled label selected value')
    add('textarea', 'cols dirname disabled form maxlength name readonly required rows wrap')
    add('menu', 'type label', [flowContent, 'li'].join(' '))
    add('noscript', '', flowContent)
    if (type !== 'html4') {
      add('wbr')
      add('ruby', '', [phrasingContent, 'rt rp'].join(' '))
      add('figcaption', '', flowContent)
      add('mark rt rp summary bdi', '', phrasingContent)
      add('canvas', 'width height', flowContent)
      add(
        'video',
        'src crossorigin poster preload autoplay mediagroup loop ' + 'muted controls width height buffered',
        [flowContent, 'track source'].join(' ')
      )
      add(
        'audio',
        'src crossorigin preload autoplay mediagroup loop muted controls ' + 'buffered volume',
        [flowContent, 'track source'].join(' ')
      )
      add('picture', '', 'img source')
      add('source', 'src srcset type media sizes')
      add('track', 'kind src srclang label default')
      add('datalist', '', [phrasingContent, 'option'].join(' '))
      add('article section nav aside main header footer', '', flowContent)
      add('hgroup', '', 'h1 h2 h3 h4 h5 h6')
      add('figure', '', [flowContent, 'figcaption'].join(' '))
      add('time', 'datetime', phrasingContent)
      add('dialog', 'open', flowContent)
      add('command', 'type label icon disabled checked radiogroup command')
      add('output', 'for form name', phrasingContent)
      add('progress', 'value max', phrasingContent)
      add('meter', 'value min max low high optimum', phrasingContent)
      add('details', 'open', [flowContent, 'summary'].join(' '))
      add('keygen', 'autofocus challenge disabled form keytype name')
    }
    if (type !== 'html5-strict') {
      addAttrs('script', 'language xml:space')
      addAttrs('style', 'xml:space')
      addAttrs('object', 'declare classid code codebase codetype archive standby align border hspace vspace')
      addAttrs('embed', 'align name hspace vspace')
      addAttrs('param', 'valuetype type')
      addAttrs('a', 'charset name rev shape coords')
      addAttrs('br', 'clear')
      addAttrs('applet', 'codebase archive code object alt name width height align hspace vspace')
      addAttrs('img', 'name longdesc align border hspace vspace')
      addAttrs('iframe', 'longdesc frameborder marginwidth marginheight scrolling align')
      addAttrs('font basefont', 'size color face')
      addAttrs('input', 'usemap align')
      addAttrs('select')
      addAttrs('textarea')
      addAttrs('h1 h2 h3 h4 h5 h6 div p legend caption', 'align')
      addAttrs('ul', 'type compact')
      addAttrs('li', 'type')
      addAttrs('ol dl menu dir', 'compact')
      addAttrs('pre', 'width xml:space')
      addAttrs('hr', 'align noshade size width')
      addAttrs('isindex', 'prompt')
      addAttrs('table', 'summary width frame rules cellspacing cellpadding align bgcolor')
      addAttrs('col', 'width align char charoff valign')
      addAttrs('colgroup', 'width align char charoff valign')
      addAttrs('thead', 'align char charoff valign')
      addAttrs('tr', 'align char charoff valign bgcolor')
      addAttrs('th', 'axis align char charoff valign nowrap bgcolor width height')
      addAttrs('form', 'accept')
      addAttrs('td', 'abbr axis scope align char charoff valign nowrap bgcolor width height')
      addAttrs('tfoot', 'align char charoff valign')
      addAttrs('tbody', 'align char charoff valign')
      addAttrs('area', 'nohref')
      addAttrs('body', 'background bgcolor text link vlink alink')
    }
    if (type !== 'html4') {
      addAttrs('input button select textarea', 'autofocus')
      addAttrs('input textarea', 'placeholder')
      addAttrs('a', 'download')
      addAttrs('link script img', 'crossorigin')
      addAttrs('img', 'loading')
      addAttrs('iframe', 'sandbox seamless allowfullscreen loading')
    }
    if (type !== 'html4') {
      each$f([schema.video, schema.audio], (item) => {
        delete item.children.audio
        delete item.children.video
      })
    }
    each$c(split$1('a form meter progress dfn'), (name) => {
      if (schema[name]) {
        delete schema[name].children[name]
      }
    })
    delete schema.caption.children.table
    delete schema.script
    mapCache[type] = schema
    return schema
  }
  const compileElementMap = (value, mode) => {
    let styles
    if (value) {
      styles = {}
      if (typeof value === 'string') {
        value = { '*': value }
      }
      each$c(value, (value, key) => {
        styles[key] = styles[key.toUpperCase()] = mode === 'map' ? makeMap$2(value, /[, ]/) : explode$2(value, /[, ]/)
      })
    }
    return styles
  }
  const Schema = (settings) => {
    var _a
    const elements = {}
    const children = {}
    let patternElements = []
    const customElementsMap = {},
      specialElements = {}
    const createLookupTable = (option, defaultValue, extendWith) => {
      let value = settings[option]
      if (!value) {
        value = mapCache[option]
        if (!value) {
          value = createMap(defaultValue, extendWith)
          mapCache[option] = value
        }
      } else {
        value = makeMap$2(value, /[, ]/, makeMap$2(value.toUpperCase(), /[, ]/))
      }
      return value
    }
    settings = settings || {}
    const schemaType = (_a = settings.schema) !== null && _a !== void 0 ? _a : 'html5'
    const schemaItems = compileSchema(schemaType)
    if (settings.verify_html === false) {
      settings.valid_elements = '*[*]'
    }
    const validStyles = compileElementMap(settings.valid_styles)
    const invalidStyles = compileElementMap(settings.invalid_styles, 'map')
    const validClasses = compileElementMap(settings.valid_classes, 'map')
    const whitespaceElementsMap = createLookupTable(
      'whitespace_elements',
      'pre script noscript style textarea video audio iframe object code'
    )
    const selfClosingElementsMap = createLookupTable(
      'self_closing_elements',
      'colgroup dd dt li option p td tfoot th thead tr'
    )
    const voidElementsMap = createLookupTable(
      'void_elements',
      'area base basefont br col frame hr img input isindex link ' + 'meta param embed source wbr track'
    )
    const boolAttrMap = createLookupTable(
      'boolean_attributes',
      'checked compact declare defer disabled ismap multiple nohref noresize ' +
        'noshade nowrap readonly selected autoplay loop controls allowfullscreen'
    )
    const nonEmptyOrMoveCaretBeforeOnEnter = 'td th iframe video audio object script code'
    const nonEmptyElementsMap = createLookupTable(
      'non_empty_elements',
      nonEmptyOrMoveCaretBeforeOnEnter + ' pre',
      voidElementsMap
    )
    const moveCaretBeforeOnEnterElementsMap = createLookupTable(
      'move_caret_before_on_enter_elements',
      nonEmptyOrMoveCaretBeforeOnEnter + ' table',
      voidElementsMap
    )
    const textBlockElementsMap = createLookupTable(
      'text_block_elements',
      'h1 h2 h3 h4 h5 h6 p div address pre form ' +
        'blockquote center dir fieldset header footer article section hgroup aside main nav figure'
    )
    const blockElementsMap = createLookupTable(
      'block_elements',
      'hr table tbody thead tfoot ' +
        'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
        'datalist select optgroup figcaption details summary',
      textBlockElementsMap
    )
    const textInlineElementsMap = createLookupTable(
      'text_inline_elements',
      'span strong b em i font s strike u var cite ' + 'dfn code mark q sup sub samp'
    )
    each$c('script noscript iframe noframes noembed title style textarea xmp plaintext'.split(' '), (name) => {
      specialElements[name] = new RegExp('</' + name + '[^>]*>', 'gi')
    })
    const patternToRegExp = (str) => new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$')
    const addValidElements = (validElements) => {
      let ei,
        el,
        ai,
        al,
        matches,
        element,
        attr,
        attrData,
        elementName,
        attrName,
        attrType,
        attributes,
        attributesOrder,
        prefix,
        outputName,
        globalAttributes,
        globalAttributesOrder,
        value
      const elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)])?$/,
        attrRuleRegExp = /^([!\-])?(\w+[\\:]:\w+|[^=~<]+)?(?:([=~<])(.*))?$/,
        hasPatternsRegExp = /[*?+]/
      if (validElements) {
        const validElementsArr = split$1(validElements, ',')
        if (elements['@']) {
          globalAttributes = elements['@'].attributes
          globalAttributesOrder = elements['@'].attributesOrder
        }
        for (ei = 0, el = validElementsArr.length; ei < el; ei++) {
          matches = elementRuleRegExp.exec(validElementsArr[ei])
          if (matches) {
            prefix = matches[1]
            elementName = matches[2]
            outputName = matches[3]
            attrData = matches[5]
            attributes = {}
            attributesOrder = []
            element = {
              attributes,
              attributesOrder,
            }
            if (prefix === '#') {
              element.paddEmpty = true
            }
            if (prefix === '-') {
              element.removeEmpty = true
            }
            if (matches[4] === '!') {
              element.removeEmptyAttrs = true
            }
            if (globalAttributes) {
              each$e(globalAttributes, (value, key) => {
                attributes[key] = value
              })
              attributesOrder.push.apply(attributesOrder, globalAttributesOrder)
            }
            if (attrData) {
              attrData = split$1(attrData, '|')
              for (ai = 0, al = attrData.length; ai < al; ai++) {
                matches = attrRuleRegExp.exec(attrData[ai])
                if (matches) {
                  attr = {}
                  attrType = matches[1]
                  attrName = matches[2].replace(/[\\:]:/g, ':')
                  prefix = matches[3]
                  value = matches[4]
                  if (attrType === '!') {
                    element.attributesRequired = element.attributesRequired || []
                    element.attributesRequired.push(attrName)
                    attr.required = true
                  }
                  if (attrType === '-') {
                    delete attributes[attrName]
                    attributesOrder.splice(inArray(attributesOrder, attrName), 1)
                    continue
                  }
                  if (prefix) {
                    if (prefix === '=') {
                      element.attributesDefault = element.attributesDefault || []
                      element.attributesDefault.push({
                        name: attrName,
                        value,
                      })
                      attr.defaultValue = value
                    }
                    if (prefix === '~') {
                      element.attributesForced = element.attributesForced || []
                      element.attributesForced.push({
                        name: attrName,
                        value,
                      })
                      attr.forcedValue = value
                    }
                    if (prefix === '<') {
                      attr.validValues = makeMap$2(value, '?')
                    }
                  }
                  if (hasPatternsRegExp.test(attrName)) {
                    element.attributePatterns = element.attributePatterns || []
                    attr.pattern = patternToRegExp(attrName)
                    element.attributePatterns.push(attr)
                  } else {
                    if (!attributes[attrName]) {
                      attributesOrder.push(attrName)
                    }
                    attributes[attrName] = attr
                  }
                }
              }
            }
            if (!globalAttributes && elementName === '@') {
              globalAttributes = attributes
              globalAttributesOrder = attributesOrder
            }
            if (outputName) {
              element.outputName = elementName
              elements[outputName] = element
            }
            if (hasPatternsRegExp.test(elementName)) {
              element.pattern = patternToRegExp(elementName)
              patternElements.push(element)
            } else {
              elements[elementName] = element
            }
          }
        }
      }
    }
    const setValidElements = (validElements) => {
      patternElements = []
      each$f(keys(elements), (name) => {
        delete elements[name]
      })
      addValidElements(validElements)
      each$c(schemaItems, (element, name) => {
        children[name] = element.children
      })
    }
    const addCustomElements = (customElements) => {
      const customElementRegExp = /^(~)?(.+)$/
      if (customElements) {
        mapCache.text_block_elements = mapCache.block_elements = null
        each$c(split$1(customElements, ','), (rule) => {
          const matches = customElementRegExp.exec(rule),
            inline = matches[1] === '~',
            cloneName = inline ? 'span' : 'div',
            name = matches[2]
          children[name] = children[cloneName]
          customElementsMap[name] = cloneName
          nonEmptyElementsMap[name.toUpperCase()] = {}
          nonEmptyElementsMap[name] = {}
          if (!inline) {
            blockElementsMap[name.toUpperCase()] = {}
            blockElementsMap[name] = {}
          }
          if (!elements[name]) {
            let customRule = elements[cloneName]
            customRule = extend$2({}, customRule)
            delete customRule.removeEmptyAttrs
            delete customRule.removeEmpty
            elements[name] = customRule
          }
          each$c(children, (element, elmName) => {
            if (element[cloneName]) {
              children[elmName] = element = extend$2({}, children[elmName])
              element[name] = element[cloneName]
            }
          })
        })
      }
    }
    const addValidChildren = (validChildren) => {
      const childRuleRegExp =
        /^([+\-]?)([A-Za-z0-9_\-.\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]+)\[([^\]]+)]$/
      mapCache[schemaType] = null
      if (validChildren) {
        each$c(split$1(validChildren, ','), (rule) => {
          const matches = childRuleRegExp.exec(rule)
          let parent, prefix
          if (matches) {
            prefix = matches[1]
            if (prefix) {
              parent = children[matches[2]]
            } else {
              parent = children[matches[2]] = { '#comment': {} }
            }
            parent = children[matches[2]]
            each$c(split$1(matches[3], '|'), (child) => {
              if (prefix === '-') {
                delete parent[child]
              } else {
                parent[child] = {}
              }
            })
          }
        })
      }
    }
    const getElementRule = (name) => {
      let element = elements[name],
        i
      if (element) {
        return element
      }
      i = patternElements.length
      while (i--) {
        element = patternElements[i]
        if (element.pattern.test(name)) {
          return element
        }
      }
    }
    if (!settings.valid_elements) {
      each$c(schemaItems, (element, name) => {
        elements[name] = {
          attributes: element.attributes,
          attributesOrder: element.attributesOrder,
        }
        children[name] = element.children
      })
      each$c(split$1('strong/b em/i'), (item) => {
        const items = split$1(item, '/')
        elements[items[1]].outputName = items[0]
      })
      each$c(textInlineElementsMap, (_val, name) => {
        if (elements[name]) {
          if (settings.padd_empty_block_inline_children) {
            elements[name].paddInEmptyBlock = true
          }
          elements[name].removeEmpty = true
        }
      })
      each$c(split$1('ol ul blockquote a table tbody'), (name) => {
        if (elements[name]) {
          elements[name].removeEmpty = true
        }
      })
      each$c(split$1('p h1 h2 h3 h4 h5 h6 th td pre div address caption li'), (name) => {
        elements[name].paddEmpty = true
      })
      each$c(split$1('span'), (name) => {
        elements[name].removeEmptyAttrs = true
      })
    } else {
      setValidElements(settings.valid_elements)
    }
    addCustomElements(settings.custom_elements)
    addValidChildren(settings.valid_children)
    addValidElements(settings.extended_valid_elements)
    addValidChildren('+ol[ul|ol],+ul[ul|ol]')
    each$c(
      {
        dd: 'dl',
        dt: 'dl',
        li: 'ul ol',
        td: 'tr',
        th: 'tr',
        tr: 'tbody thead tfoot',
        tbody: 'table',
        thead: 'table',
        tfoot: 'table',
        legend: 'fieldset',
        area: 'map',
        param: 'video audio object',
      },
      (parents, item) => {
        if (elements[item]) {
          elements[item].parentsRequired = split$1(parents)
        }
      }
    )
    if (settings.invalid_elements) {
      each$c(explode$2(settings.invalid_elements), (item) => {
        if (elements[item]) {
          delete elements[item]
        }
      })
    }
    if (!getElementRule('span')) {
      addValidElements('span[!data-mce-type|*]')
    }
    const getValidStyles = constant(validStyles)
    const getInvalidStyles = constant(invalidStyles)
    const getValidClasses = constant(validClasses)
    const getBoolAttrs = constant(boolAttrMap)
    const getBlockElements = constant(blockElementsMap)
    const getTextBlockElements = constant(textBlockElementsMap)
    const getTextInlineElements = constant(textInlineElementsMap)
    const getVoidElements = constant(Object.seal(voidElementsMap))
    const getSelfClosingElements = constant(selfClosingElementsMap)
    const getNonEmptyElements = constant(nonEmptyElementsMap)
    const getMoveCaretBeforeOnEnterElements = constant(moveCaretBeforeOnEnterElementsMap)
    const getWhitespaceElements = constant(whitespaceElementsMap)
    const getSpecialElements = constant(Object.seal(specialElements))
    const isValidChild = (name, child) => {
      const parent = children[name.toLowerCase()]
      return !!(parent && parent[child.toLowerCase()])
    }
    const isValid = (name, attr) => {
      let attrPatterns, i
      const rule = getElementRule(name)
      if (rule) {
        if (attr) {
          if (rule.attributes[attr]) {
            return true
          }
          attrPatterns = rule.attributePatterns
          if (attrPatterns) {
            i = attrPatterns.length
            while (i--) {
              if (attrPatterns[i].pattern.test(attr)) {
                return true
              }
            }
          }
        } else {
          return true
        }
      }
      return false
    }
    const getCustomElements = constant(customElementsMap)
    return {
      type: schemaType,
      children,
      elements,
      getValidStyles,
      getValidClasses,
      getBlockElements,
      getInvalidStyles,
      getVoidElements,
      getTextBlockElements,
      getTextInlineElements,
      getBoolAttrs,
      getElementRule,
      getSelfClosingElements,
      getNonEmptyElements,
      getMoveCaretBeforeOnEnterElements,
      getWhitespaceElements,
      getSpecialElements,
      isValidChild,
      isValid,
      getCustomElements,
      addValidElements,
      setValidElements,
      addCustomElements,
      addValidChildren,
    }
  }

  const Styles = (settings, schema) => {
    const urlOrStrRegExp =
      /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi
    const styleRegExp = /\s*([^:]+):\s*([^;]+);?/g
    const trimRightRegExp = /\s+$/
    let i
    const encodingLookup = {}
    let validStyles
    let invalidStyles
    const invisibleChar = zeroWidth
    settings = settings || {}
    if (schema) {
      validStyles = schema.getValidStyles()
      invalidStyles = schema.getInvalidStyles()
    }
    const encodingItems = (`\\" \\' \\; \\: ; : ` + invisibleChar).split(' ')
    for (i = 0; i < encodingItems.length; i++) {
      encodingLookup[encodingItems[i]] = invisibleChar + i
      encodingLookup[invisibleChar + i] = encodingItems[i]
    }
    const self = {
      parse: (css) => {
        const styles = {}
        let matches, name, value, isEncoded
        const urlConverter = settings.url_converter
        const urlConverterScope = settings.url_converter_scope || self
        const compress = (prefix, suffix, noJoin) => {
          const top = styles[prefix + '-top' + suffix]
          if (!top) {
            return
          }
          const right = styles[prefix + '-right' + suffix]
          if (!right) {
            return
          }
          const bottom = styles[prefix + '-bottom' + suffix]
          if (!bottom) {
            return
          }
          const left = styles[prefix + '-left' + suffix]
          if (!left) {
            return
          }
          const box = [top, right, bottom, left]
          i = box.length - 1
          while (i--) {
            if (box[i] !== box[i + 1]) {
              break
            }
          }
          if (i > -1 && noJoin) {
            return
          }
          styles[prefix + suffix] = i === -1 ? box[0] : box.join(' ')
          delete styles[prefix + '-top' + suffix]
          delete styles[prefix + '-right' + suffix]
          delete styles[prefix + '-bottom' + suffix]
          delete styles[prefix + '-left' + suffix]
        }
        const canCompress = (key) => {
          let value = styles[key],
            i
          if (!value) {
            return
          }
          value = value.split(' ')
          i = value.length
          while (i--) {
            if (value[i] !== value[0]) {
              return false
            }
          }
          styles[key] = value[0]
          return true
        }
        const compress2 = (target, a, b, c) => {
          if (!canCompress(a)) {
            return
          }
          if (!canCompress(b)) {
            return
          }
          if (!canCompress(c)) {
            return
          }
          styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]
          delete styles[a]
          delete styles[b]
          delete styles[c]
        }
        const encode = (str) => {
          isEncoded = true
          return encodingLookup[str]
        }
        const decode = (str, keepSlashes) => {
          if (isEncoded) {
            str = str.replace(/\uFEFF[0-9]/g, (str) => {
              return encodingLookup[str]
            })
          }
          if (!keepSlashes) {
            str = str.replace(/\\([\'\";:])/g, '$1')
          }
          return str
        }
        const decodeSingleHexSequence = (escSeq) => {
          return String.fromCharCode(parseInt(escSeq.slice(1), 16))
        }
        const decodeHexSequences = (value) => {
          return value.replace(/\\[0-9a-f]+/gi, decodeSingleHexSequence)
        }
        const processUrl = (match, url, url2, url3, str, str2) => {
          str = str || str2
          if (str) {
            str = decode(str)
            return `'` + str.replace(/\'/g, `\\'`) + `'`
          }
          url = decode(url || url2 || url3)
          if (!settings.allow_script_urls) {
            const scriptUrl = url.replace(/[\s\r\n]+/g, '')
            if (/(java|vb)script:/i.test(scriptUrl)) {
              return ''
            }
            if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
              return ''
            }
          }
          if (urlConverter) {
            url = urlConverter.call(urlConverterScope, url, 'style')
          }
          return `url('` + url.replace(/\'/g, `\\'`) + `')`
        }
        if (css) {
          css = css.replace(/[\u0000-\u001F]/g, '')
          css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, (str) => {
            return str.replace(/[;:]/g, encode)
          })
          while ((matches = styleRegExp.exec(css))) {
            styleRegExp.lastIndex = matches.index + matches[0].length
            name = matches[1].replace(trimRightRegExp, '').toLowerCase()
            value = matches[2].replace(trimRightRegExp, '')
            if (name && value) {
              name = decodeHexSequences(name)
              value = decodeHexSequences(value)
              if (name.indexOf(invisibleChar) !== -1 || name.indexOf('"') !== -1) {
                continue
              }
              if (!settings.allow_script_urls && (name === 'behavior' || /expression\s*\(|\/\*|\*\//.test(value))) {
                continue
              }
              if (name === 'font-weight' && value === '700') {
                value = 'bold'
              } else if (name === 'color' || name === 'background-color') {
                value = value.toLowerCase()
              }
              value = value.replace(urlOrStrRegExp, processUrl)
              styles[name] = isEncoded ? decode(value, true) : value
            }
          }
          compress('border', '', true)
          compress('border', '-width')
          compress('border', '-color')
          compress('border', '-style')
          compress('padding', '')
          compress('margin', '')
          compress2('border', 'border-width', 'border-style', 'border-color')
          if (styles.border === 'medium none') {
            delete styles.border
          }
          if (styles['border-image'] === 'none') {
            delete styles['border-image']
          }
        }
        return styles
      },
      serialize: (styles, elementName) => {
        let css = ''
        const serializeStyles = (name) => {
          let value
          const styleList = validStyles[name]
          if (styleList) {
            for (let i = 0, l = styleList.length; i < l; i++) {
              name = styleList[i]
              value = styles[name]
              if (value) {
                css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'
              }
            }
          }
        }
        const isValid = (name, elementName) => {
          let styleMap = invalidStyles['*']
          if (styleMap && styleMap[name]) {
            return false
          }
          styleMap = invalidStyles[elementName]
          return !(styleMap && styleMap[name])
        }
        if (elementName && validStyles) {
          serializeStyles('*')
          serializeStyles(elementName)
        } else {
          each$e(styles, (value, name) => {
            if (value && (!invalidStyles || isValid(name, elementName))) {
              css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'
            }
          })
        }
        return css
      },
    }
    return self
  }

  const deprecated = {
    keyLocation: true,
    layerX: true,
    layerY: true,
    returnValue: true,
    webkitMovementX: true,
    webkitMovementY: true,
    keyIdentifier: true,
    mozPressure: true,
  }
  const isNativeEvent = (event) => event instanceof Event || isFunction(event.initEvent)
  const hasIsDefaultPrevented = (event) => event.isDefaultPrevented === always || event.isDefaultPrevented === never
  const needsNormalizing = (event) => isNullable(event.preventDefault) || isNativeEvent(event)
  const clone$3 = (originalEvent, data) => {
    const event = data !== null && data !== void 0 ? data : {}
    for (const name in originalEvent) {
      if (!has$2(deprecated, name)) {
        event[name] = originalEvent[name]
      }
    }
    if (isNonNullable(event.composedPath)) {
      event.composedPath = () => originalEvent.composedPath()
    }
    return event
  }
  const normalize$3 = (type, originalEvent, fallbackTarget, data) => {
    var _a
    const event = clone$3(originalEvent, data)
    event.type = type
    if (isNullable(event.target)) {
      event.target = (_a = event.srcElement) !== null && _a !== void 0 ? _a : fallbackTarget
    }
    if (needsNormalizing(originalEvent)) {
      event.preventDefault = () => {
        event.defaultPrevented = true
        event.isDefaultPrevented = always
        if (isFunction(originalEvent.preventDefault)) {
          originalEvent.preventDefault()
        }
      }
      event.stopPropagation = () => {
        event.cancelBubble = true
        event.isPropagationStopped = always
        if (isFunction(originalEvent.stopPropagation)) {
          originalEvent.stopPropagation()
        }
      }
      event.stopImmediatePropagation = () => {
        event.isImmediatePropagationStopped = always
        event.stopPropagation()
      }
      if (!hasIsDefaultPrevented(event)) {
        event.isDefaultPrevented = event.defaultPrevented === true ? always : never
        event.isPropagationStopped = event.cancelBubble === true ? always : never
        event.isImmediatePropagationStopped = never
      }
    }
    return event
  }

  const eventExpandoPrefix = 'mce-data-'
  const mouseEventRe = /^(?:mouse|contextmenu)|click/
  const addEvent = (target, name, callback, capture) => {
    if (target.addEventListener) {
      target.addEventListener(name, callback, capture || false)
    } else if (target.attachEvent) {
      target.attachEvent('on' + name, callback)
    }
  }
  const removeEvent = (target, name, callback, capture) => {
    if (target.removeEventListener) {
      target.removeEventListener(name, callback, capture || false)
    } else if (target.detachEvent) {
      target.detachEvent('on' + name, callback)
    }
  }
  const isMouseEvent = (event) => isNonNullable(event) && mouseEventRe.test(event.type)
  const fix = (originalEvent, data) => {
    const event = normalize$3(originalEvent.type, originalEvent, document, data)
    if (isMouseEvent(originalEvent) && isUndefined(originalEvent.pageX) && !isUndefined(originalEvent.clientX)) {
      const eventDoc = event.target.ownerDocument || document
      const doc = eventDoc.documentElement
      const body = eventDoc.body
      const mouseEvent = event
      mouseEvent.pageX =
        originalEvent.clientX +
        ((doc && doc.scrollLeft) || (body && body.scrollLeft) || 0) -
        ((doc && doc.clientLeft) || (body && body.clientLeft) || 0)
      mouseEvent.pageY =
        originalEvent.clientY +
        ((doc && doc.scrollTop) || (body && body.scrollTop) || 0) -
        ((doc && doc.clientTop) || (body && body.clientTop) || 0)
    }
    return event
  }
  const bindOnReady = (win, callback, eventUtils) => {
    const doc = win.document,
      event = { type: 'ready' }
    if (eventUtils.domLoaded) {
      callback(event)
      return
    }
    const isDocReady = () => {
      return doc.readyState === 'complete' || (doc.readyState === 'interactive' && doc.body)
    }
    const readyHandler = () => {
      removeEvent(win, 'DOMContentLoaded', readyHandler)
      removeEvent(win, 'load', readyHandler)
      if (!eventUtils.domLoaded) {
        eventUtils.domLoaded = true
        callback(event)
      }
      win = null
    }
    if (isDocReady()) {
      readyHandler()
    } else {
      addEvent(win, 'DOMContentLoaded', readyHandler)
    }
    if (!eventUtils.domLoaded) {
      addEvent(win, 'load', readyHandler)
    }
  }
  class EventUtils {
    constructor() {
      this.domLoaded = false
      this.events = {}
      this.count = 1
      this.expando = eventExpandoPrefix + (+new Date()).toString(32)
      this.hasMouseEnterLeave = 'onmouseenter' in document.documentElement
      this.hasFocusIn = 'onfocusin' in document.documentElement
      this.count = 1
    }
    bind(target, names, callback, scope) {
      const self = this
      let id, callbackList, i, name, fakeName, nativeHandler, capture
      const win = window
      const defaultNativeHandler = (evt) => {
        self.executeHandlers(fix(evt || win.event), id)
      }
      if (!target || target.nodeType === 3 || target.nodeType === 8) {
        return
      }
      if (!target[self.expando]) {
        id = self.count++
        target[self.expando] = id
        self.events[id] = {}
      } else {
        id = target[self.expando]
      }
      scope = scope || target
      const namesList = names.split(' ')
      i = namesList.length
      while (i--) {
        name = namesList[i]
        nativeHandler = defaultNativeHandler
        fakeName = capture = false
        if (name === 'DOMContentLoaded') {
          name = 'ready'
        }
        if (self.domLoaded && name === 'ready' && target.readyState === 'complete') {
          callback.call(scope, fix({ type: name }))
          continue
        }
        if (!self.hasMouseEnterLeave) {
          fakeName = self.mouseEnterLeave[name]
          if (fakeName) {
            nativeHandler = (evt) => {
              const current = evt.currentTarget
              let related = evt.relatedTarget
              if (related && current.contains) {
                related = current.contains(related)
              } else {
                while (related && related !== current) {
                  related = related.parentNode
                }
              }
              if (!related) {
                evt = fix(evt || win.event)
                evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'
                evt.target = current
                self.executeHandlers(evt, id)
              }
            }
          }
        }
        if (!self.hasFocusIn && (name === 'focusin' || name === 'focusout')) {
          capture = true
          fakeName = name === 'focusin' ? 'focus' : 'blur'
          nativeHandler = (evt) => {
            evt = fix(evt || win.event)
            evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'
            self.executeHandlers(evt, id)
          }
        }
        callbackList = self.events[id][name]
        if (!callbackList) {
          self.events[id][name] = callbackList = [
            {
              func: callback,
              scope,
            },
          ]
          callbackList.fakeName = fakeName
          callbackList.capture = capture
          callbackList.nativeHandler = nativeHandler
          if (name === 'ready') {
            bindOnReady(target, nativeHandler, self)
          } else {
            addEvent(target, fakeName || name, nativeHandler, capture)
          }
        } else {
          if (name === 'ready' && self.domLoaded) {
            callback(fix({ type: name }))
          } else {
            callbackList.push({
              func: callback,
              scope,
            })
          }
        }
      }
      target = callbackList = null
      return callback
    }
    unbind(target, names, callback) {
      let callbackList, i, ci, name, eventMap
      if (!target || target.nodeType === 3 || target.nodeType === 8) {
        return this
      }
      const id = target[this.expando]
      if (id) {
        eventMap = this.events[id]
        if (names) {
          const namesList = names.split(' ')
          i = namesList.length
          while (i--) {
            name = namesList[i]
            callbackList = eventMap[name]
            if (callbackList) {
              if (callback) {
                ci = callbackList.length
                while (ci--) {
                  if (callbackList[ci].func === callback) {
                    const nativeHandler = callbackList.nativeHandler
                    const fakeName = callbackList.fakeName,
                      capture = callbackList.capture
                    callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1))
                    callbackList.nativeHandler = nativeHandler
                    callbackList.fakeName = fakeName
                    callbackList.capture = capture
                    eventMap[name] = callbackList
                  }
                }
              }
              if (!callback || callbackList.length === 0) {
                delete eventMap[name]
                removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture)
              }
            }
          }
        } else {
          each$e(eventMap, (callbackList, name) => {
            removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture)
          })
          eventMap = {}
        }
        for (name in eventMap) {
          if (has$2(eventMap, name)) {
            return this
          }
        }
        delete this.events[id]
        try {
          delete target[this.expando]
        } catch (ex) {
          target[this.expando] = null
        }
      }
      return this
    }
    fire(target, name, args) {
      return this.dispatch(target, name, args)
    }
    dispatch(target, name, args) {
      let id
      if (!target || target.nodeType === 3 || target.nodeType === 8) {
        return this
      }
      const event = fix(
        {
          type: name,
          target,
        },
        args
      )
      do {
        id = target[this.expando]
        if (id) {
          this.executeHandlers(event, id)
        }
        target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow
      } while (target && !event.isPropagationStopped())
      return this
    }
    clean(target) {
      let i, children
      if (!target || target.nodeType === 3 || target.nodeType === 8) {
        return this
      }
      if (target[this.expando]) {
        this.unbind(target)
      }
      if (!target.getElementsByTagName) {
        target = target.document
      }
      if (target && target.getElementsByTagName) {
        this.unbind(target)
        children = target.getElementsByTagName('*')
        i = children.length
        while (i--) {
          target = children[i]
          if (target[this.expando]) {
            this.unbind(target)
          }
        }
      }
      return this
    }
    destroy() {
      this.events = {}
    }
    cancel(e) {
      if (e) {
        e.preventDefault()
        e.stopImmediatePropagation()
      }
      return false
    }
    executeHandlers(evt, id) {
      const container = this.events[id]
      const callbackList = container && container[evt.type]
      if (callbackList) {
        for (let i = 0, l = callbackList.length; i < l; i++) {
          const callback = callbackList[i]
          if (callback && callback.func.call(callback.scope, evt) === false) {
            evt.preventDefault()
          }
          if (evt.isImmediatePropagationStopped()) {
            return
          }
        }
      }
    }
  }
  EventUtils.Event = new EventUtils()

  const each$b = Tools.each
  const grep = Tools.grep
  const internalStyleName = 'data-mce-style'
  const legacySetAttribute = (elm, name, value) => {
    if (isNullable(value) || value === '') {
      remove$b(elm, name)
    } else {
      set$2(elm, name, value)
    }
  }
  const setupAttrHooks = (styles, settings, getContext) => {
    const keepValues = settings.keep_values
    const keepUrlHook = {
      set: (elm, value, name) => {
        const sugarElm = SugarElement.fromDom(elm)
        if (isFunction(settings.url_converter) && isNonNullable(value)) {
          value = settings.url_converter.call(settings.url_converter_scope || getContext(), value, name, elm[0])
        }
        const internalName = 'data-mce-' + name
        legacySetAttribute(sugarElm, internalName, value)
        legacySetAttribute(sugarElm, name, value)
      },
      get: (elm, name) => {
        const sugarElm = SugarElement.fromDom(elm)
        return get$9(sugarElm, 'data-mce-' + name) || get$9(sugarElm, name)
      },
    }
    const attrHooks = {
      style: {
        set: (elm, value) => {
          const sugarElm = SugarElement.fromDom(elm)
          if (isObject(value)) {
            setAll(sugarElm, value)
            return
          }
          if (keepValues) {
            legacySetAttribute(sugarElm, internalStyleName, value)
          }
          remove$b(sugarElm, 'style')
          if (isString(value)) {
            setAll(sugarElm, styles.parse(value))
          }
        },
        get: (elm) => {
          const sugarElm = SugarElement.fromDom(elm)
          const value = get$9(sugarElm, internalStyleName) || get$9(sugarElm, 'style')
          return styles.serialize(styles.parse(value), name(sugarElm))
        },
      },
    }
    if (keepValues) {
      attrHooks.href = attrHooks.src = keepUrlHook
    }
    return attrHooks
  }
  const updateInternalStyleAttr = (styles, elm) => {
    const rawValue = get$9(elm, 'style')
    const value = styles.serialize(styles.parse(rawValue), name(elm))
    legacySetAttribute(elm, internalStyleName, value)
  }
  const findNodeIndex = (node, normalized) => {
    let idx = 0,
      lastNodeType,
      nodeType
    if (node) {
      for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
        nodeType = node.nodeType
        if (normalized && nodeType === 3) {
          if (nodeType === lastNodeType || !node.nodeValue.length) {
            continue
          }
        }
        idx++
        lastNodeType = nodeType
      }
    }
    return idx
  }
  const numericalCssMap = Tools.makeMap('fill-opacity font-weight line-height opacity orphans widows z-index zoom', ' ')
  const camelCaseToHyphens = (name) => name.replace(/[A-Z]/g, (v) => '-' + v.toLowerCase())
  const DOMUtils = (doc, settings = {}) => {
    const addedStyles = {}
    const win = window
    const files = {}
    let counter = 0
    const stdMode = true
    const boxModel = true
    const styleSheetLoader = instance.forElement(SugarElement.fromDom(doc), {
      contentCssCors: settings.contentCssCors,
      referrerPolicy: settings.referrerPolicy,
    })
    const boundEvents = []
    const schema = settings.schema ? settings.schema : Schema({})
    const styles = Styles(
      {
        url_converter: settings.url_converter,
        url_converter_scope: settings.url_converter_scope,
      },
      settings.schema
    )
    const events = settings.ownEvents ? new EventUtils() : EventUtils.Event
    const blockElementsMap = schema.getBlockElements()
    const isBlock = (node) => {
      if (isString(node)) {
        return has$2(blockElementsMap, node)
      } else {
        return isElement$6(node) && has$2(blockElementsMap, node.nodeName)
      }
    }
    const get = (elm) => (elm && doc && isString(elm) ? doc.getElementById(elm) : elm)
    const _get = (elm) => {
      const value = get(elm)
      return isNonNullable(value) ? SugarElement.fromDom(value) : null
    }
    const getAttrib = (elm, name, defaultVal) => {
      let value
      const $elm = _get(elm)
      if (isNonNullable($elm) && isElement$7($elm)) {
        const hook = attrHooks[name]
        if (hook && hook.get) {
          value = hook.get($elm.dom, name)
        } else {
          value = get$9($elm, name)
        }
      }
      return isNonNullable(value) ? value : defaultVal !== null && defaultVal !== void 0 ? defaultVal : ''
    }
    const getAttribs = (elm) => {
      const node = get(elm)
      return isNullable(node) ? [] : node.attributes
    }
    const setAttrib = (elm, name, value) => {
      run(elm, (e) => {
        if (isElement$6(e)) {
          const $elm = SugarElement.fromDom(e)
          if (value === '') {
            value = null
          }
          const originalValue = get$9($elm, name)
          const hook = attrHooks[name]
          if (hook && hook.set) {
            hook.set($elm.dom, value, name)
          } else {
            legacySetAttribute($elm, name, value)
          }
          if (originalValue !== value && settings.onSetAttrib) {
            settings.onSetAttrib({
              attrElm: $elm,
              attrName: name,
              attrValue: value,
            })
          }
        }
      })
    }
    const clone = (node, deep) => {
      return node.cloneNode(deep)
    }
    const getRoot = () => settings.root_element || doc.body
    const getViewPort = (argWin) => {
      const vp = getBounds(argWin)
      return {
        x: vp.x,
        y: vp.y,
        w: vp.width,
        h: vp.height,
      }
    }
    const getPos$1 = (elm, rootElm) => getPos(doc.body, get(elm), rootElm)
    const setStyle = (elm, name, value) => {
      const convertStyleToString = (cssValue, cssName) => {
        if (isString(cssValue)) {
          return cssValue
        } else if (isNumber(cssValue)) {
          return has$2(numericalCssMap, cssName) ? cssValue + '' : cssValue + 'px'
        } else {
          return map$2(cssValue, convertStyleToString)
        }
      }
      const applyStyle = ($elm, cssName, cssValue) => {
        const normalizedName = camelCaseToHyphens(cssName)
        if (isNullable(cssValue) || cssValue === '') {
          remove$7($elm, normalizedName)
        } else {
          set$1($elm, normalizedName, convertStyleToString(cssValue, normalizedName))
        }
      }
      run(elm, (e) => {
        const $elm = SugarElement.fromDom(e)
        if (isString(name)) {
          applyStyle($elm, name, value)
        } else {
          each$e(name, (v, n) => {
            applyStyle($elm, n, v)
          })
        }
        if (settings.update_styles) {
          updateInternalStyleAttr(styles, $elm)
        }
      })
    }
    const setStyles = (elm, stylesArg) => {
      setStyle(elm, stylesArg)
    }
    const getStyle = (elm, name, computed) => {
      const $elm = get(elm)
      if (isNullable($elm) || !isElement$6($elm)) {
        return undefined
      }
      if (computed) {
        return get$7(SugarElement.fromDom($elm), camelCaseToHyphens(name))
      } else {
        name = name.replace(/-(\D)/g, (a, b) => b.toUpperCase())
        if (name === 'float') {
          name = 'cssFloat'
        }
        return $elm.style ? $elm.style[name] : undefined
      }
    }
    const getSize = (elm) => {
      let w, h
      const $elm = get(elm)
      w = getStyle($elm, 'width')
      h = getStyle($elm, 'height')
      if (w.indexOf('px') === -1) {
        w = 0
      }
      if (h.indexOf('px') === -1) {
        h = 0
      }
      return {
        w: parseInt(w, 10) || $elm.offsetWidth || $elm.clientWidth,
        h: parseInt(h, 10) || $elm.offsetHeight || $elm.clientHeight,
      }
    }
    const getRect = (elm) => {
      const $elm = get(elm)
      const pos = getPos$1($elm)
      const size = getSize($elm)
      return {
        x: pos.x,
        y: pos.y,
        w: size.w,
        h: size.h,
      }
    }
    const is = (elm, selector) => {
      if (!elm) {
        return false
      }
      const elms = isArray$1(elm) ? elm : [elm]
      return exists(elms, (e) => {
        return is$1(SugarElement.fromDom(e), selector)
      })
    }
    const getParents = (elm, selector, root, collect) => {
      const result = []
      let selectorVal
      let node = get(elm)
      collect = collect === undefined
      root = root || (getRoot().nodeName !== 'BODY' ? getRoot().parentNode : null)
      if (isString(selector)) {
        selectorVal = selector
        if (selector === '*') {
          selector = isElement$6
        } else {
          selector = (node) => is(node, selectorVal)
        }
      }
      while (node) {
        if (node === root || isNullable(node.nodeType) || isDocument$1(node) || isDocumentFragment(node)) {
          break
        }
        if (!selector || selector(node)) {
          if (collect) {
            result.push(node)
          } else {
            return [node]
          }
        }
        node = node.parentNode
      }
      return collect ? result : null
    }
    const getParent = (node, selector, root) => {
      const parents = getParents(node, selector, root, false)
      return parents && parents.length > 0 ? parents[0] : null
    }
    const _findSib = (node, selector, name) => {
      let func = selector
      if (node) {
        if (isString(selector)) {
          func = (node) => {
            return is(node, selector)
          }
        }
        for (node = node[name]; node; node = node[name]) {
          if (isFunction(func) && func(node)) {
            return node
          }
        }
      }
      return null
    }
    const getNext = (node, selector) => _findSib(node, selector, 'nextSibling')
    const getPrev = (node, selector) => _findSib(node, selector, 'previousSibling')
    const select = (selector, scope) => {
      var _a, _b
      const elm =
        (_b = (_a = get(scope)) !== null && _a !== void 0 ? _a : settings.root_element) !== null && _b !== void 0
          ? _b
          : doc
      return from(elm.querySelectorAll(selector))
    }
    const run = function (elm, func, scope) {
      const context = scope !== null && scope !== void 0 ? scope : this
      const node = isString(elm) ? get(elm) : elm
      if (!node) {
        return false
      }
      if (isArray$1(node) && (node.length || node.length === 0)) {
        const result = []
        each$b(node, (elm, i) => {
          if (elm) {
            result.push(func.call(context, isString(elm) ? get(elm) : elm, i))
          }
        })
        return result
      } else {
        return func.call(context, node)
      }
    }
    const setAttribs = (elm, attrs) => {
      run(elm, ($elm) => {
        each$e(attrs, (value, name) => {
          setAttrib($elm, name, value)
        })
      })
    }
    const setHTML = (elm, html) => {
      run(elm, (e) => {
        const $elm = SugarElement.fromDom(e)
        set($elm, html)
      })
    }
    const add = (parentElm, name, attrs, html, create) =>
      run(parentElm, (parentElm) => {
        const newElm = isString(name) ? doc.createElement(name) : name
        if (isNonNullable(attrs)) {
          setAttribs(newElm, attrs)
        }
        if (html) {
          if (!isString(html) && html.nodeType) {
            newElm.appendChild(html)
          } else if (isString(html)) {
            setHTML(newElm, html)
          }
        }
        return !create ? parentElm.appendChild(newElm) : newElm
      })
    const create = (name, attrs, html) => add(doc.createElement(name), name, attrs, html, true)
    const decode = Entities.decode
    const encode = Entities.encodeAllRaw
    const createHTML = (name, attrs, html = '') => {
      let outHtml = '',
        key
      outHtml += '<' + name
      for (key in attrs) {
        if (hasNonNullableKey(attrs, key)) {
          outHtml += ' ' + key + '="' + encode(attrs[key]) + '"'
        }
      }
      if (isEmpty$3(html) && has$2(schema.getVoidElements(), name)) {
        return outHtml + ' />'
      } else {
        return outHtml + '>' + html + '</' + name + '>'
      }
    }
    const createFragment = (html) => {
      let node
      const container = doc.createElement('div')
      const frag = doc.createDocumentFragment()
      frag.appendChild(container)
      if (html) {
        container.innerHTML = html
      }
      while ((node = container.firstChild)) {
        frag.appendChild(node)
      }
      frag.removeChild(container)
      return frag
    }
    const remove = (node, keepChildren) => {
      return run(node, (n) => {
        const $node = SugarElement.fromDom(n)
        if (keepChildren) {
          each$f(children($node), (child) => {
            if (isText$a(child) && child.dom.length === 0) {
              remove$6(child)
            } else {
              before$3($node, child)
            }
          })
        }
        remove$6($node)
        return $node.dom
      })
    }
    const removeAllAttribs = (e) =>
      run(e, (e) => {
        const attrs = e.attributes
        for (let i = attrs.length - 1; i >= 0; i--) {
          e.removeAttributeNode(attrs.item(i))
        }
      })
    const parseStyle = (cssText) => styles.parse(cssText)
    const serializeStyle = (stylesArg, name) => styles.serialize(stylesArg, name)
    const addStyle = (cssText) => {
      let head, styleElm
      if (self !== DOMUtils.DOM && doc === document) {
        if (addedStyles[cssText]) {
          return
        }
        addedStyles[cssText] = true
      }
      styleElm = doc.getElementById('mceDefaultStyles')
      if (!styleElm) {
        styleElm = doc.createElement('style')
        styleElm.id = 'mceDefaultStyles'
        styleElm.type = 'text/css'
        head = doc.getElementsByTagName('head')[0]
        if (head.firstChild) {
          head.insertBefore(styleElm, head.firstChild)
        } else {
          head.appendChild(styleElm)
        }
      }
      if (styleElm.styleSheet) {
        styleElm.styleSheet.cssText += cssText
      } else {
        styleElm.appendChild(doc.createTextNode(cssText))
      }
    }
    const loadCSS = (urls) => {
      if (!urls) {
        urls = ''
      }
      each$f(urls.split(','), (url) => {
        files[url] = true
        styleSheetLoader.load(url).catch(noop)
      })
    }
    const toggleClass = (elm, cls, state) => {
      run(elm, (e) => {
        if (isElement$6(e)) {
          const $elm = SugarElement.fromDom(e)
          const classes = cls.split(' ')
          each$f(classes, (c) => {
            if (isNonNullable(state)) {
              const fn = state ? add$2 : remove$8
              fn($elm, c)
            } else {
              toggle$1($elm, c)
            }
          })
        }
      })
    }
    const addClass = (elm, cls) => {
      toggleClass(elm, cls, true)
    }
    const removeClass = (elm, cls) => {
      toggleClass(elm, cls, false)
    }
    const hasClass = (elm, cls) => {
      const $elm = _get(elm)
      const classes = cls.split(' ')
      return forall(classes, (c) => has($elm, c))
    }
    const show = (elm) => {
      run(elm, (e) => remove$7(SugarElement.fromDom(e), 'display'))
    }
    const hide = (elm) => {
      run(elm, (e) => set$1(SugarElement.fromDom(e), 'display', 'none'))
    }
    const isHidden = (elm) => {
      const $elm = _get(elm)
      return is$2(getRaw$1($elm, 'display'), 'none')
    }
    const uniqueId = (prefix) => (!prefix ? 'mce_' : prefix) + counter++
    const getOuterHTML = (elm) => {
      const $elm = _get(elm)
      return isElement$6($elm.dom) ? $elm.dom.outerHTML : getOuter($elm)
    }
    const setOuterHTML = (elm, html) => {
      run(elm, ($elm) => {
        if (isElement$6($elm)) {
          $elm.outerHTML = html
        }
      })
    }
    const insertAfter = (node, reference) => {
      const referenceNode = get(reference)
      return run(node, (node) => {
        const parent = referenceNode.parentNode
        const nextSibling = referenceNode.nextSibling
        if (nextSibling) {
          parent.insertBefore(node, nextSibling)
        } else {
          parent.appendChild(node)
        }
        return node
      })
    }
    const replace = (newElm, oldElm, keepChildren) =>
      run(oldElm, (oldElm) => {
        if (isArray$1(oldElm)) {
          newElm = newElm.cloneNode(true)
        }
        if (keepChildren) {
          each$b(grep(oldElm.childNodes), (node) => {
            newElm.appendChild(node)
          })
        }
        return oldElm.parentNode.replaceChild(newElm, oldElm)
      })
    const rename = (elm, name) => {
      let newElm
      if (elm.nodeName !== name.toUpperCase()) {
        newElm = create(name)
        each$b(getAttribs(elm), (attrNode) => {
          setAttrib(newElm, attrNode.nodeName, getAttrib(elm, attrNode.nodeName))
        })
        replace(newElm, elm, true)
      }
      return newElm || elm
    }
    const findCommonAncestor = (a, b) => {
      let ps = a,
        pe
      while (ps) {
        pe = b
        while (pe && ps !== pe) {
          pe = pe.parentNode
        }
        if (ps === pe) {
          break
        }
        ps = ps.parentNode
      }
      if (!ps && a.ownerDocument) {
        return a.ownerDocument.documentElement
      }
      return ps
    }
    const isNonEmptyElement = (node) => {
      if (isElement$6(node)) {
        const isNamedAnchor = node.nodeName.toLowerCase() === 'a' && !getAttrib(node, 'href') && getAttrib(node, 'id')
        if (getAttrib(node, 'name') || getAttrib(node, 'data-mce-bookmark') || isNamedAnchor) {
          return true
        }
      }
      return false
    }
    const isEmpty = (node, elements) => {
      let type,
        name,
        brCount = 0
      if (isNonEmptyElement(node)) {
        return false
      }
      node = node.firstChild
      if (node) {
        const walker = new DomTreeWalker(node, node.parentNode)
        const whitespace = schema ? schema.getWhitespaceElements() : {}
        elements = elements || (schema ? schema.getNonEmptyElements() : null)
        do {
          type = node.nodeType
          if (isElement$6(node)) {
            const bogusVal = node.getAttribute('data-mce-bogus')
            if (bogusVal) {
              node = walker.next(bogusVal === 'all')
              continue
            }
            name = node.nodeName.toLowerCase()
            if (elements && elements[name]) {
              if (name === 'br') {
                brCount++
                node = walker.next()
                continue
              }
              return false
            }
            if (isNonEmptyElement(node)) {
              return false
            }
          }
          if (type === 8) {
            return false
          }
          if (type === 3 && !isWhitespaceText(node.nodeValue)) {
            return false
          }
          if (
            type === 3 &&
            node.parentNode &&
            whitespace[node.parentNode.nodeName] &&
            isWhitespaceText(node.nodeValue)
          ) {
            return false
          }
          node = walker.next()
        } while (node)
      }
      return brCount <= 1
    }
    const createRng = () => doc.createRange()
    const split = (parentElm, splitElm, replacementElm) => {
      let range = createRng()
      let beforeFragment
      let afterFragment
      let parentNode
      if (parentElm && splitElm) {
        range.setStart(parentElm.parentNode, findNodeIndex(parentElm))
        range.setEnd(splitElm.parentNode, findNodeIndex(splitElm))
        beforeFragment = range.extractContents()
        range = createRng()
        range.setStart(splitElm.parentNode, findNodeIndex(splitElm) + 1)
        range.setEnd(parentElm.parentNode, findNodeIndex(parentElm) + 1)
        afterFragment = range.extractContents()
        parentNode = parentElm.parentNode
        parentNode.insertBefore(trimNode(self, beforeFragment), parentElm)
        if (replacementElm) {
          parentNode.insertBefore(replacementElm, parentElm)
        } else {
          parentNode.insertBefore(splitElm, parentElm)
        }
        parentNode.insertBefore(trimNode(self, afterFragment), parentElm)
        remove(parentElm)
        return replacementElm || splitElm
      }
    }
    const bind = (target, name, func, scope) => {
      if (isArray$1(target)) {
        let i = target.length
        const rv = []
        while (i--) {
          rv[i] = bind(target[i], name, func, scope)
        }
        return rv
      } else {
        if (settings.collect && (target === doc || target === win)) {
          boundEvents.push([target, name, func, scope])
        }
        return events.bind(target, name, func, scope || self)
      }
    }
    const unbind = (target, name, func) => {
      if (isArray$1(target)) {
        let i = target.length
        const rv = []
        while (i--) {
          rv[i] = unbind(target[i], name, func)
        }
        return rv
      } else {
        if (boundEvents.length > 0 && (target === doc || target === win)) {
          let i = boundEvents.length
          while (i--) {
            const item = boundEvents[i]
            if (target === item[0] && (!name || name === item[1]) && (!func || func === item[2])) {
              events.unbind(item[0], item[1], item[2])
            }
          }
        }
        return events.unbind(target, name, func)
      }
    }
    const dispatch = (target, name, evt) => events.dispatch(target, name, evt)
    const fire = (target, name, evt) => events.dispatch(target, name, evt)
    const getContentEditable = (node) => {
      if (node && isElement$6(node)) {
        const contentEditable = node.getAttribute('data-mce-contenteditable')
        if (contentEditable && contentEditable !== 'inherit') {
          return contentEditable
        }
        return node.contentEditable !== 'inherit' ? node.contentEditable : null
      } else {
        return null
      }
    }
    const getContentEditableParent = (node) => {
      const root = getRoot()
      let state = null
      for (; node && node !== root; node = node.parentNode) {
        state = getContentEditable(node)
        if (state !== null) {
          break
        }
      }
      return state
    }
    const destroy = () => {
      if (boundEvents.length > 0) {
        let i = boundEvents.length
        while (i--) {
          const item = boundEvents[i]
          events.unbind(item[0], item[1], item[2])
        }
      }
      each$e(files, (_, url) => {
        styleSheetLoader.unload(url)
        delete files[url]
      })
    }
    const isChildOf = (node, parent) => {
      return node === parent || parent.contains(node)
    }
    const dumpRng = (r) =>
      'startContainer: ' +
      r.startContainer.nodeName +
      ', startOffset: ' +
      r.startOffset +
      ', endContainer: ' +
      r.endContainer.nodeName +
      ', endOffset: ' +
      r.endOffset
    const self = {
      doc,
      settings,
      win,
      files,
      stdMode,
      boxModel,
      styleSheetLoader,
      boundEvents,
      styles,
      schema,
      events,
      isBlock,
      root: null,
      clone,
      getRoot,
      getViewPort,
      getRect,
      getSize,
      getParent,
      getParents,
      get,
      getNext,
      getPrev,
      select,
      is,
      add,
      create,
      createHTML,
      createFragment,
      remove,
      setStyle,
      getStyle,
      setStyles,
      removeAllAttribs,
      setAttrib,
      setAttribs,
      getAttrib,
      getPos: getPos$1,
      parseStyle,
      serializeStyle,
      addStyle,
      loadCSS,
      addClass,
      removeClass,
      hasClass,
      toggleClass,
      show,
      hide,
      isHidden,
      uniqueId,
      setHTML,
      getOuterHTML,
      setOuterHTML,
      decode,
      encode,
      insertAfter,
      replace,
      rename,
      findCommonAncestor,
      run,
      getAttribs,
      isEmpty,
      createRng,
      nodeIndex: findNodeIndex,
      split,
      bind: bind,
      unbind: unbind,
      fire,
      dispatch,
      getContentEditable,
      getContentEditableParent,
      destroy,
      isChildOf,
      dumpRng,
    }
    const attrHooks = setupAttrHooks(styles, settings, constant(self))
    return self
  }
  DOMUtils.DOM = DOMUtils(document)
  DOMUtils.nodeIndex = findNodeIndex

  const DOM$b = DOMUtils.DOM
  const QUEUED = 0
  const LOADING = 1
  const LOADED = 2
  const FAILED = 3
  class ScriptLoader {
    constructor(settings = {}) {
      this.states = {}
      this.queue = []
      this.scriptLoadedCallbacks = {}
      this.queueLoadedCallbacks = []
      this.loading = false
      this.settings = settings
    }
    _setReferrerPolicy(referrerPolicy) {
      this.settings.referrerPolicy = referrerPolicy
    }
    loadScript(url) {
      return new Promise((resolve, reject) => {
        const dom = DOM$b
        let elm
        const cleanup = () => {
          dom.remove(id)
          if (elm) {
            elm.onerror = elm.onload = elm = null
          }
        }
        const done = () => {
          cleanup()
          resolve()
        }
        const error = () => {
          cleanup()
          reject('Failed to load script: ' + url)
        }
        const id = dom.uniqueId()
        elm = document.createElement('script')
        elm.id = id
        elm.type = 'text/javascript'
        elm.src = Tools._addCacheSuffix(url)
        if (this.settings.referrerPolicy) {
          dom.setAttrib(elm, 'referrerpolicy', this.settings.referrerPolicy)
        }
        elm.onload = done
        elm.onerror = error
        ;(document.getElementsByTagName('head')[0] || document.body).appendChild(elm)
      })
    }
    isDone(url) {
      return this.states[url] === LOADED
    }
    markDone(url) {
      this.states[url] = LOADED
    }
    add(url) {
      const self = this
      self.queue.push(url)
      const state = self.states[url]
      if (state === undefined) {
        self.states[url] = QUEUED
      }
      return new Promise((resolve, reject) => {
        if (!self.scriptLoadedCallbacks[url]) {
          self.scriptLoadedCallbacks[url] = []
        }
        self.scriptLoadedCallbacks[url].push({
          resolve,
          reject,
        })
      })
    }
    load(url) {
      return this.add(url)
    }
    remove(url) {
      delete this.states[url]
      delete this.scriptLoadedCallbacks[url]
    }
    loadQueue() {
      const queue = this.queue
      this.queue = []
      return this.loadScripts(queue)
    }
    loadScripts(scripts) {
      const self = this
      const execCallbacks = (name, url) => {
        get$a(self.scriptLoadedCallbacks, url).each((callbacks) => {
          each$f(callbacks, (callback) => callback[name](url))
        })
        delete self.scriptLoadedCallbacks[url]
      }
      const processResults = (results) => {
        const failures = filter$6(results, (result) => result.status === 'rejected')
        if (failures.length > 0) {
          return Promise.reject(bind$3(failures, ({ reason }) => (isArray$1(reason) ? reason : [reason])))
        } else {
          return Promise.resolve()
        }
      }
      const load = (urls) =>
        Promise.allSettled(
          map$3(urls, (url) => {
            if (self.states[url] === LOADED) {
              execCallbacks('resolve', url)
              return Promise.resolve()
            } else if (self.states[url] === FAILED) {
              execCallbacks('reject', url)
              return Promise.reject(url)
            } else {
              self.states[url] = LOADING
              return self.loadScript(url).then(
                () => {
                  self.states[url] = LOADED
                  execCallbacks('resolve', url)
                  const queue = self.queue
                  if (queue.length > 0) {
                    self.queue = []
                    return load(queue).then(processResults)
                  }
                },
                () => {
                  self.states[url] = FAILED
                  execCallbacks('reject', url)
                  return Promise.reject(url)
                }
              )
            }
          })
        )
      const processQueue = (urls) => {
        self.loading = true
        return load(urls).then((results) => {
          self.loading = false
          const nextQueuedItem = self.queueLoadedCallbacks.shift()
          Optional.from(nextQueuedItem).each(call)
          return processResults(results)
        })
      }
      const uniqueScripts = stringArray(scripts)
      if (self.loading) {
        return new Promise((resolve, reject) => {
          self.queueLoadedCallbacks.push(() => processQueue(uniqueScripts).then(resolve, reject))
        })
      } else {
        return processQueue(uniqueScripts)
      }
    }
  }
  ScriptLoader.ScriptLoader = new ScriptLoader()

  const Cell = (initial) => {
    let value = initial
    const get = () => {
      return value
    }
    const set = (v) => {
      value = v
    }
    return {
      get,
      set,
    }
  }

  const isRaw = (str) => isObject(str) && has$2(str, 'raw')
  const isTokenised = (str) => isArray$1(str) && str.length > 1
  const data = {}
  const currentCode = Cell('en')
  const getLanguageData = () => get$a(data, currentCode.get())
  const getData$1 = () => map$2(data, (value) => ({ ...value }))
  const setCode = (newCode) => {
    if (newCode) {
      currentCode.set(newCode)
    }
  }
  const getCode = () => currentCode.get()
  const add$1 = (code, items) => {
    let langData = data[code]
    if (!langData) {
      data[code] = langData = {}
    }
    each$e(items, (translation, name) => {
      langData[name.toLowerCase()] = translation
    })
  }
  const translate = (text) => {
    const langData = getLanguageData().getOr({})
    const toString = (obj) => {
      if (isFunction(obj)) {
        return Object.prototype.toString.call(obj)
      }
      return !isEmpty(obj) ? '' + obj : ''
    }
    const isEmpty = (text) => text === '' || text === null || text === undefined
    const getLangData = (text) => {
      const textstr = toString(text)
      return get$a(langData, textstr.toLowerCase()).map(toString).getOr(textstr)
    }
    const removeContext = (str) => str.replace(/{context:\w+}$/, '')
    if (isEmpty(text)) {
      return ''
    }
    if (isRaw(text)) {
      return toString(text.raw)
    }
    if (isTokenised(text)) {
      const values = text.slice(1)
      const substitued = getLangData(text[0]).replace(/\{([0-9]+)\}/g, ($1, $2) =>
        has$2(values, $2) ? toString(values[$2]) : $1
      )
      return removeContext(substitued)
    }
    return removeContext(getLangData(text))
  }
  const isRtl$1 = () =>
    getLanguageData()
      .bind((items) => get$a(items, '_dir'))
      .exists((dir) => dir === 'rtl')
  const hasCode = (code) => has$2(data, code)
  const I18n = {
    getData: getData$1,
    setCode,
    getCode,
    add: add$1,
    translate,
    isRtl: isRtl$1,
    hasCode,
  }

  const AddOnManager = () => {
    const items = []
    const urls = {}
    const lookup = {}
    const _listeners = []
    const runListeners = (name, state) => {
      const matchedListeners = filter$6(_listeners, (listener) => listener.name === name && listener.state === state)
      each$f(matchedListeners, (listener) => listener.resolve())
    }
    const isLoaded = (name) => has$2(urls, name)
    const isAdded = (name) => has$2(lookup, name)
    const get = (name) => {
      if (lookup[name]) {
        return lookup[name].instance
      }
      return undefined
    }
    const loadLanguagePack = (name, languages) => {
      const language = I18n.getCode()
      const wrappedLanguages = ',' + (languages || '') + ','
      if (!language || (languages && wrappedLanguages.indexOf(',' + language + ',') === -1)) {
        return
      }
      ScriptLoader.ScriptLoader.add(urls[name] + '/langs/' + language + '.js')
    }
    const requireLangPack = (name, languages) => {
      if (AddOnManager.languageLoad !== false) {
        if (isLoaded(name)) {
          loadLanguagePack(name, languages)
        } else {
          waitFor(name, 'loaded').then(() => loadLanguagePack(name, languages))
        }
      }
    }
    const add = (id, addOn) => {
      items.push(addOn)
      lookup[id] = { instance: addOn }
      runListeners(id, 'added')
      return addOn
    }
    const remove = (name) => {
      delete urls[name]
      delete lookup[name]
    }
    const createUrl = (baseUrl, dep) => {
      if (isString(dep)) {
        return isString(baseUrl)
          ? {
              prefix: '',
              resource: dep,
              suffix: '',
            }
          : {
              prefix: baseUrl.prefix,
              resource: dep,
              suffix: baseUrl.suffix,
            }
      } else {
        return dep
      }
    }
    const load = (name, addOnUrl) => {
      if (urls[name]) {
        return Promise.resolve()
      }
      let urlString = isString(addOnUrl) ? addOnUrl : addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix
      if (urlString.indexOf('/') !== 0 && urlString.indexOf('://') === -1) {
        urlString = AddOnManager.baseURL + '/' + urlString
      }
      urls[name] = urlString.substring(0, urlString.lastIndexOf('/'))
      const done = () => {
        runListeners(name, 'loaded')
        return Promise.resolve()
      }
      if (lookup[name]) {
        return done()
      } else {
        return ScriptLoader.ScriptLoader.add(urlString).then(done)
      }
    }
    const waitFor = (name, state = 'added') => {
      if (state === 'added' && isAdded(name)) {
        return Promise.resolve()
      } else if (state === 'loaded' && isLoaded(name)) {
        return Promise.resolve()
      } else {
        return new Promise((resolve) => {
          _listeners.push({
            name,
            state,
            resolve,
          })
        })
      }
    }
    return {
      items,
      urls,
      lookup,
      get,
      requireLangPack,
      add,
      remove,
      createUrl,
      load,
      waitFor,
    }
  }
  AddOnManager.languageLoad = true
  AddOnManager.baseURL = ''
  AddOnManager.PluginManager = AddOnManager()
  AddOnManager.ThemeManager = AddOnManager()
  AddOnManager.ModelManager = AddOnManager()

  const singleton = (doRevoke) => {
    const subject = Cell(Optional.none())
    const revoke = () => subject.get().each(doRevoke)
    const clear = () => {
      revoke()
      subject.set(Optional.none())
    }
    const isSet = () => subject.get().isSome()
    const get = () => subject.get()
    const set = (s) => {
      revoke()
      subject.set(Optional.some(s))
    }
    return {
      clear,
      isSet,
      get,
      set,
    }
  }
  const value$2 = () => {
    const subject = singleton(noop)
    const on = (f) => subject.get().each(f)
    return {
      ...subject,
      on,
    }
  }

  const first$1 = (fn, rate) => {
    let timer = null
    const cancel = () => {
      if (!isNull(timer)) {
        clearTimeout(timer)
        timer = null
      }
    }
    const throttle = (...args) => {
      if (isNull(timer)) {
        timer = setTimeout(() => {
          timer = null
          fn.apply(null, args)
        }, rate)
      }
    }
    return {
      cancel,
      throttle,
    }
  }
  const last$1 = (fn, rate) => {
    let timer = null
    const cancel = () => {
      if (!isNull(timer)) {
        clearTimeout(timer)
        timer = null
      }
    }
    const throttle = (...args) => {
      cancel()
      timer = setTimeout(() => {
        timer = null
        fn.apply(null, args)
      }, rate)
    }
    return {
      cancel,
      throttle,
    }
  }

  const annotation = constant('mce-annotation')
  const dataAnnotation = constant('data-mce-annotation')
  const dataAnnotationId = constant('data-mce-annotation-uid')
  const dataAnnotationActive = constant('data-mce-annotation-active')
  const dataAnnotationClasses = constant('data-mce-annotation-classes')
  const dataAnnotationAttributes = constant('data-mce-annotation-attrs')

  const isRoot$1 = (root) => (node) => eq(node, root)
  const identify = (editor, annotationName) => {
    const rng = editor.selection.getRng()
    const start = SugarElement.fromDom(rng.startContainer)
    const root = SugarElement.fromDom(editor.getBody())
    const selector = annotationName.fold(
      () => '.' + annotation(),
      (an) => `[${dataAnnotation()}="${an}"]`
    )
    const newStart = child$1(start, rng.startOffset).getOr(start)
    const closest = closest$3(newStart, selector, isRoot$1(root))
    const getAttr = (c, property) => {
      if (has$1(c, property)) {
        return Optional.some(get$9(c, property))
      } else {
        return Optional.none()
      }
    }
    return closest.bind((c) =>
      getAttr(c, `${dataAnnotationId()}`).bind((uid) =>
        getAttr(c, `${dataAnnotation()}`).map((name) => {
          const elements = findMarkers(editor, uid)
          return {
            uid,
            name,
            elements,
          }
        })
      )
    )
  }
  const isAnnotation = (elem) => isElement$7(elem) && has(elem, annotation())
  const isBogusElement = (elem, root) =>
    has$1(elem, 'data-mce-bogus') || ancestor$1(elem, '[data-mce-bogus="all"]', isRoot$1(root))
  const findMarkers = (editor, uid) => {
    const body = SugarElement.fromDom(editor.getBody())
    const descendants$1 = descendants(body, `[${dataAnnotationId()}="${uid}"]`)
    return filter$6(descendants$1, (descendant) => !isBogusElement(descendant, body))
  }
  const findAll = (editor, name) => {
    const body = SugarElement.fromDom(editor.getBody())
    const markers = descendants(body, `[${dataAnnotation()}="${name}"]`)
    const directory = {}
    each$f(markers, (m) => {
      if (!isBogusElement(m, body)) {
        const uid = get$9(m, dataAnnotationId())
        const nodesAlready = get$a(directory, uid).getOr([])
        directory[uid] = nodesAlready.concat([m])
      }
    })
    return directory
  }

  const setup$x = (editor, registry) => {
    const changeCallbacks = Cell({})
    const initData = () => ({
      listeners: [],
      previous: value$2(),
    })
    const withCallbacks = (name, f) => {
      updateCallbacks(name, (data) => {
        f(data)
        return data
      })
    }
    const updateCallbacks = (name, f) => {
      const callbackMap = changeCallbacks.get()
      const data = get$a(callbackMap, name).getOrThunk(initData)
      const outputData = f(data)
      callbackMap[name] = outputData
      changeCallbacks.set(callbackMap)
    }
    const fireCallbacks = (name, uid, elements) => {
      withCallbacks(name, (data) => {
        each$f(data.listeners, (f) =>
          f(true, name, {
            uid,
            nodes: map$3(elements, (elem) => elem.dom),
          })
        )
      })
    }
    const fireNoAnnotation = (name) => {
      withCallbacks(name, (data) => {
        each$f(data.listeners, (f) => f(false, name))
      })
    }
    const toggleActiveAttr = (uid, state) => {
      each$f(findMarkers(editor, uid), (elem) => {
        if (state) {
          set$2(elem, dataAnnotationActive(), 'true')
        } else {
          remove$b(elem, dataAnnotationActive())
        }
      })
    }
    const onNodeChange = last$1(() => {
      const annotations = sort(registry.getNames())
      each$f(annotations, (name) => {
        updateCallbacks(name, (data) => {
          const prev = data.previous.get()
          identify(editor, Optional.some(name)).fold(
            () => {
              prev.each((uid) => {
                fireNoAnnotation(name)
                data.previous.clear()
                toggleActiveAttr(uid, false)
              })
            },
            ({ uid, name, elements }) => {
              if (!is$2(prev, uid)) {
                prev.each((uid) => toggleActiveAttr(uid, false))
                fireCallbacks(name, uid, elements)
                data.previous.set(uid)
                toggleActiveAttr(uid, true)
              }
            }
          )
          return {
            previous: data.previous,
            listeners: data.listeners,
          }
        })
      })
    }, 30)
    editor.on('remove', () => {
      onNodeChange.cancel()
    })
    editor.on('NodeChange', () => {
      onNodeChange.throttle()
    })
    const addListener = (name, f) => {
      updateCallbacks(name, (data) => ({
        previous: data.previous,
        listeners: data.listeners.concat([f]),
      }))
    }
    return { addListener }
  }

  const setup$w = (editor, registry) => {
    const dataAnnotation$1 = dataAnnotation()
    const identifyParserNode = (node) => Optional.from(node.attr(dataAnnotation$1)).bind(registry.lookup)
    const removeDirectAnnotation = (node) => {
      var _a, _b
      node.attr(dataAnnotationId(), null)
      node.attr(dataAnnotation(), null)
      node.attr(dataAnnotationActive(), null)
      const customAttrNames = Optional.from(node.attr(dataAnnotationAttributes()))
        .map((names) => names.split(','))
        .getOr([])
      const customClasses = Optional.from(node.attr(dataAnnotationClasses()))
        .map((names) => names.split(','))
        .getOr([])
      each$f(customAttrNames, (name) => node.attr(name, null))
      const classList =
        (_b = (_a = node.attr('class')) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0
          ? _b
          : []
      const newClassList = difference(classList, [annotation()].concat(customClasses))
      node.attr('class', newClassList.length > 0 ? newClassList.join(' ') : null)
      node.attr(dataAnnotationClasses(), null)
      node.attr(dataAnnotationAttributes(), null)
    }
    editor.serializer.addTempAttr(dataAnnotationActive())
    editor.serializer.addAttributeFilter(dataAnnotation$1, (nodes) => {
      for (const node of nodes) {
        identifyParserNode(node).each((settings) => {
          if (settings.persistent === false) {
            if (node.name === 'span') {
              node.unwrap()
            } else {
              removeDirectAnnotation(node)
            }
          }
        })
      }
    })
  }

  const create$c = () => {
    const annotations = {}
    const register = (name, settings) => {
      annotations[name] = {
        name,
        settings,
      }
    }
    const lookup = (name) => get$a(annotations, name).map((a) => a.settings)
    const getNames = () => keys(annotations)
    return {
      register,
      lookup,
      getNames,
    }
  }

  let unique = 0
  const generate$1 = (prefix) => {
    const date = new Date()
    const time = date.getTime()
    const random = Math.floor(Math.random() * 1000000000)
    unique++
    return prefix + '_' + random + unique + String(time)
  }

  const add = (element, classes) => {
    each$f(classes, (x) => {
      add$2(element, x)
    })
  }
  const remove$5 = (element, classes) => {
    each$f(classes, (x) => {
      remove$8(element, x)
    })
  }

  const clone$2 = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep))
  const shallow$1 = (original) => clone$2(original, false)
  const deep$1 = (original) => clone$2(original, true)
  const shallowAs = (original, tag) => {
    const nu = SugarElement.fromTag(tag)
    const attributes = clone$4(original)
    setAll$1(nu, attributes)
    return nu
  }
  const mutate = (original, tag) => {
    const nu = shallowAs(original, tag)
    after$4(original, nu)
    const children$1 = children(original)
    append(nu, children$1)
    remove$6(original)
    return nu
  }

  const TextWalker = (startNode, rootNode, isBoundary = never) => {
    const walker = new DomTreeWalker(startNode, rootNode)
    const walk = (direction) => {
      let next
      do {
        next = walker[direction]()
      } while (next && !isText$9(next) && !isBoundary(next))
      return Optional.from(next).filter(isText$9)
    }
    return {
      current: () => Optional.from(walker.current()).filter(isText$9),
      next: () => walk('next'),
      prev: () => walk('prev'),
      prev2: () => walk('prev2'),
    }
  }

  const TextSeeker = (dom, isBoundary) => {
    const isBlockBoundary = isBoundary
      ? isBoundary
      : (node) => dom.isBlock(node) || isBr$6(node) || isContentEditableFalse$b(node)
    const walk = (node, offset, walker, process) => {
      if (isText$9(node)) {
        const newOffset = process(node, offset, node.data)
        if (newOffset !== -1) {
          return Optional.some({
            container: node,
            offset: newOffset,
          })
        }
      }
      return walker().bind((next) => walk(next.container, next.offset, walker, process))
    }
    const backwards = (node, offset, process, root) => {
      const walker = TextWalker(node, root, isBlockBoundary)
      return walk(
        node,
        offset,
        () =>
          walker.prev().map((prev) => ({
            container: prev,
            offset: prev.length,
          })),
        process
      ).getOrNull()
    }
    const forwards = (node, offset, process, root) => {
      const walker = TextWalker(node, root, isBlockBoundary)
      return walk(
        node,
        offset,
        () =>
          walker.next().map((next) => ({
            container: next,
            offset: 0,
          })),
        process
      ).getOrNull()
    }
    return {
      backwards,
      forwards,
    }
  }

  const round$2 = Math.round
  const clone$1 = (rect) => {
    if (!rect) {
      return {
        left: 0,
        top: 0,
        bottom: 0,
        right: 0,
        width: 0,
        height: 0,
      }
    }
    return {
      left: round$2(rect.left),
      top: round$2(rect.top),
      bottom: round$2(rect.bottom),
      right: round$2(rect.right),
      width: round$2(rect.width),
      height: round$2(rect.height),
    }
  }
  const collapse = (rect, toStart) => {
    rect = clone$1(rect)
    if (toStart) {
      rect.right = rect.left
    } else {
      rect.left = rect.left + rect.width
      rect.right = rect.left
    }
    rect.width = 0
    return rect
  }
  const isEqual = (rect1, rect2) =>
    rect1.left === rect2.left && rect1.top === rect2.top && rect1.bottom === rect2.bottom && rect1.right === rect2.right
  const isValidOverflow = (overflowY, rect1, rect2) =>
    overflowY >= 0 && overflowY <= Math.min(rect1.height, rect2.height) / 2
  const isAbove$1 = (rect1, rect2) => {
    const halfHeight = Math.min(rect2.height / 2, rect1.height / 2)
    if (rect1.bottom - halfHeight < rect2.top) {
      return true
    }
    if (rect1.top > rect2.bottom) {
      return false
    }
    return isValidOverflow(rect2.top - rect1.bottom, rect1, rect2)
  }
  const isBelow$1 = (rect1, rect2) => {
    if (rect1.top > rect2.bottom) {
      return true
    }
    if (rect1.bottom < rect2.top) {
      return false
    }
    return isValidOverflow(rect2.bottom - rect1.top, rect1, rect2)
  }
  const containsXY = (rect, clientX, clientY) =>
    clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom
  const boundingClientRectFromRects = (rects) => {
    return foldl(
      rects,
      (acc, rect) => {
        return acc.fold(
          () => Optional.some(rect),
          (prevRect) => {
            const left = Math.min(rect.left, prevRect.left)
            const top = Math.min(rect.top, prevRect.top)
            const right = Math.max(rect.right, prevRect.right)
            const bottom = Math.max(rect.bottom, prevRect.bottom)
            return Optional.some({
              top,
              right,
              bottom,
              left,
              width: right - left,
              height: bottom - top,
            })
          }
        )
      },
      Optional.none()
    )
  }
  const distanceToRectEdgeFromXY = (rect, x, y) => {
    const cx = Math.max(Math.min(x, rect.left + rect.width), rect.left)
    const cy = Math.max(Math.min(y, rect.top + rect.height), rect.top)
    return Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))
  }
  const overlapY = (r1, r2) => Math.max(0, Math.min(r1.bottom, r2.bottom) - Math.max(r1.top, r2.top))

  const clamp$2 = (value, min, max) => Math.min(Math.max(value, min), max)

  const getSelectedNode = (range) => {
    const startContainer = range.startContainer,
      startOffset = range.startOffset
    if (
      startContainer === range.endContainer &&
      startContainer.hasChildNodes() &&
      range.endOffset === startOffset + 1
    ) {
      return startContainer.childNodes[startOffset]
    }
    return null
  }
  const getNode$1 = (container, offset) => {
    if (isElement$6(container) && container.hasChildNodes()) {
      const childNodes = container.childNodes
      const safeOffset = clamp$2(offset, 0, childNodes.length - 1)
      return childNodes[safeOffset]
    } else {
      return container
    }
  }
  const getNodeUnsafe = (container, offset) => {
    if (offset < 0 && isElement$6(container) && container.hasChildNodes()) {
      return undefined
    } else {
      return getNode$1(container, offset)
    }
  }

  const extendingChars = new RegExp(
    '[\u0300-\u036f\u0483-\u0487\u0488-\u0489\u0591-\u05bd\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05c7\u0610-\u061a' +
      '\u064b-\u065f\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7-\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0' +
      '\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e3-\u0902\u093a\u093c' +
      '\u0941-\u0948\u094d\u0951-\u0957\u0962-\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2-\u09e3' +
      '\u0a01-\u0a02\u0a3c\u0a41-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a51\u0a70-\u0a71\u0a75\u0a81-\u0a82\u0abc' +
      '\u0ac1-\u0ac5\u0ac7-\u0ac8\u0acd\u0ae2-\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57' +
      '\u0b62-\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c00\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56' +
      '\u0c62-\u0c63\u0c81\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc-\u0ccd\u0cd5-\u0cd6\u0ce2-\u0ce3\u0d01\u0d3e\u0d41-\u0d44' +
      '\u0d4d\u0d57\u0d62-\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9' +
      '\u0ebb-\u0ebc\u0ec8-\u0ecd\u0f18-\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86-\u0f87\u0f8d-\u0f97' +
      '\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039-\u103a\u103d-\u103e\u1058-\u1059\u105e-\u1060\u1071-\u1074' +
      '\u1082\u1085-\u1086\u108d\u109d\u135d-\u135f\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17b4-\u17b5' +
      '\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193b\u1a17-\u1a18' +
      '\u1a1b\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1ab0-\u1abd\u1ABE\u1b00-\u1b03\u1b34' +
      '\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80-\u1b81\u1ba2-\u1ba5\u1ba8-\u1ba9\u1bab-\u1bad\u1be6\u1be8-\u1be9' +
      '\u1bed\u1bef-\u1bf1\u1c2c-\u1c33\u1c36-\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1cf4\u1cf8-\u1cf9' +
      '\u1dc0-\u1df5\u1dfc-\u1dff\u200c-\u200d\u20d0-\u20dc\u20DD-\u20E0\u20e1\u20E2-\u20E4\u20e5-\u20f0\u2cef-\u2cf1' +
      '\u2d7f\u2de0-\u2dff\u302a-\u302d\u302e-\u302f\u3099-\u309a\ua66f\uA670-\uA672\ua674-\ua67d\ua69e-\ua69f\ua6f0-\ua6f1' +
      '\ua802\ua806\ua80b\ua825-\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc' +
      '\ua9e5\uaa29-\uaa2e\uaa31-\uaa32\uaa35-\uaa36\uaa43\uaa4c\uaa7c\uaab0\uaab2-\uaab4\uaab7-\uaab8\uaabe-\uaabf\uaac1' +
      '\uaaec-\uaaed\uaaf6\uabe5\uabe8\uabed\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\uff9e-\uff9f]'
  )
  const isExtendingChar = (ch) => typeof ch === 'string' && ch.charCodeAt(0) >= 768 && extendingChars.test(ch)

  const or = (...args) => {
    return (x) => {
      for (let i = 0; i < args.length; i++) {
        if (args[i](x)) {
          return true
        }
      }
      return false
    }
  }
  const and = (...args) => {
    return (x) => {
      for (let i = 0; i < args.length; i++) {
        if (!args[i](x)) {
          return false
        }
      }
      return true
    }
  }

  const isElement$4 = isElement$6
  const isCaretCandidate$2 = isCaretCandidate$3
  const isBlock$1 = matchStyleValues('display', 'block table')
  const isFloated = matchStyleValues('float', 'left right')
  const isValidElementCaretCandidate = and(isElement$4, isCaretCandidate$2, not(isFloated))
  const isNotPre = not(matchStyleValues('white-space', 'pre pre-line pre-wrap'))
  const isText$6 = isText$9
  const isBr$3 = isBr$6
  const nodeIndex$1 = DOMUtils.nodeIndex
  const resolveIndex$1 = getNodeUnsafe
  const createRange$1 = (doc) => ('createRange' in doc ? doc.createRange() : DOMUtils.DOM.createRng())
  const isWhiteSpace$1 = (chr) => chr && /[\r\n\t ]/.test(chr)
  const isRange = (rng) => !!rng.setStart && !!rng.setEnd
  const isHiddenWhiteSpaceRange = (range) => {
    const container = range.startContainer
    const offset = range.startOffset
    if (isWhiteSpace$1(range.toString()) && isNotPre(container.parentNode) && isText$9(container)) {
      const text = container.data
      if (isWhiteSpace$1(text[offset - 1]) || isWhiteSpace$1(text[offset + 1])) {
        return true
      }
    }
    return false
  }
  const getBrClientRect = (brNode) => {
    const doc = brNode.ownerDocument
    const rng = createRange$1(doc)
    const nbsp$1 = doc.createTextNode(nbsp)
    const parentNode = brNode.parentNode
    parentNode.insertBefore(nbsp$1, brNode)
    rng.setStart(nbsp$1, 0)
    rng.setEnd(nbsp$1, 1)
    const clientRect = clone$1(rng.getBoundingClientRect())
    parentNode.removeChild(nbsp$1)
    return clientRect
  }
  const getBoundingClientRectWebKitText = (rng) => {
    const sc = rng.startContainer
    const ec = rng.endContainer
    const so = rng.startOffset
    const eo = rng.endOffset
    if (sc === ec && isText$9(ec) && so === 0 && eo === 1) {
      const newRng = rng.cloneRange()
      newRng.setEndAfter(ec)
      return getBoundingClientRect$1(newRng)
    } else {
      return null
    }
  }
  const isZeroRect = (r) => r.left === 0 && r.right === 0 && r.top === 0 && r.bottom === 0
  const getBoundingClientRect$1 = (item) => {
    let clientRect
    const clientRects = item.getClientRects()
    if (clientRects.length > 0) {
      clientRect = clone$1(clientRects[0])
    } else {
      clientRect = clone$1(item.getBoundingClientRect())
    }
    if (!isRange(item) && isBr$3(item) && isZeroRect(clientRect)) {
      return getBrClientRect(item)
    }
    if (isZeroRect(clientRect) && isRange(item)) {
      return getBoundingClientRectWebKitText(item)
    }
    return clientRect
  }
  const collapseAndInflateWidth = (clientRect, toStart) => {
    const newClientRect = collapse(clientRect, toStart)
    newClientRect.width = 1
    newClientRect.right = newClientRect.left + 1
    return newClientRect
  }
  const getCaretPositionClientRects = (caretPosition) => {
    const clientRects = []
    const addUniqueAndValidRect = (clientRect) => {
      if (clientRect.height === 0) {
        return
      }
      if (clientRects.length > 0) {
        if (isEqual(clientRect, clientRects[clientRects.length - 1])) {
          return
        }
      }
      clientRects.push(clientRect)
    }
    const addCharacterOffset = (container, offset) => {
      const range = createRange$1(container.ownerDocument)
      if (offset < container.data.length) {
        if (isExtendingChar(container.data[offset])) {
          return clientRects
        }
        if (isExtendingChar(container.data[offset - 1])) {
          range.setStart(container, offset)
          range.setEnd(container, offset + 1)
          if (!isHiddenWhiteSpaceRange(range)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false))
            return clientRects
          }
        }
      }
      if (offset > 0) {
        range.setStart(container, offset - 1)
        range.setEnd(container, offset)
        if (!isHiddenWhiteSpaceRange(range)) {
          addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false))
        }
      }
      if (offset < container.data.length) {
        range.setStart(container, offset)
        range.setEnd(container, offset + 1)
        if (!isHiddenWhiteSpaceRange(range)) {
          addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), true))
        }
      }
    }
    const container = caretPosition.container()
    const offset = caretPosition.offset()
    if (isText$6(container)) {
      addCharacterOffset(container, offset)
      return clientRects
    }
    if (isElement$4(container)) {
      if (caretPosition.isAtEnd()) {
        const node = resolveIndex$1(container, offset)
        if (isText$6(node)) {
          addCharacterOffset(node, node.data.length)
        }
        if (isValidElementCaretCandidate(node) && !isBr$3(node)) {
          addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false))
        }
      } else {
        const node = resolveIndex$1(container, offset)
        if (isText$6(node)) {
          addCharacterOffset(node, 0)
        }
        if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
          addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false))
          return clientRects
        }
        const beforeNode = resolveIndex$1(caretPosition.container(), caretPosition.offset() - 1)
        if (isValidElementCaretCandidate(beforeNode) && !isBr$3(beforeNode)) {
          if (isBlock$1(beforeNode) || isBlock$1(node) || !isValidElementCaretCandidate(node)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(beforeNode), false))
          }
        }
        if (isValidElementCaretCandidate(node)) {
          addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), true))
        }
      }
    }
    return clientRects
  }
  const CaretPosition = (container, offset, clientRects) => {
    const isAtStart = () => {
      if (isText$6(container)) {
        return offset === 0
      }
      return offset === 0
    }
    const isAtEnd = () => {
      if (isText$6(container)) {
        return offset >= container.data.length
      }
      return offset >= container.childNodes.length
    }
    const toRange = () => {
      const range = createRange$1(container.ownerDocument)
      range.setStart(container, offset)
      range.setEnd(container, offset)
      return range
    }
    const getClientRects = () => {
      if (!clientRects) {
        clientRects = getCaretPositionClientRects(CaretPosition(container, offset))
      }
      return clientRects
    }
    const isVisible = () => getClientRects().length > 0
    const isEqual = (caretPosition) =>
      caretPosition && container === caretPosition.container() && offset === caretPosition.offset()
    const getNode = (before) => resolveIndex$1(container, before ? offset - 1 : offset)
    return {
      container: constant(container),
      offset: constant(offset),
      toRange,
      getClientRects,
      isVisible,
      isAtStart,
      isAtEnd,
      isEqual,
      getNode,
    }
  }
  CaretPosition.fromRangeStart = (range) => CaretPosition(range.startContainer, range.startOffset)
  CaretPosition.fromRangeEnd = (range) => CaretPosition(range.endContainer, range.endOffset)
  CaretPosition.after = (node) => CaretPosition(node.parentNode, nodeIndex$1(node) + 1)
  CaretPosition.before = (node) => CaretPosition(node.parentNode, nodeIndex$1(node))
  CaretPosition.isAbove = (pos1, pos2) =>
    lift2(head(pos2.getClientRects()), last$3(pos1.getClientRects()), isAbove$1).getOr(false)
  CaretPosition.isBelow = (pos1, pos2) =>
    lift2(last$3(pos2.getClientRects()), head(pos1.getClientRects()), isBelow$1).getOr(false)
  CaretPosition.isAtStart = (pos) => (pos ? pos.isAtStart() : false)
  CaretPosition.isAtEnd = (pos) => (pos ? pos.isAtEnd() : false)
  CaretPosition.isTextPosition = (pos) => (pos ? isText$9(pos.container()) : false)
  CaretPosition.isElementPosition = (pos) => CaretPosition.isTextPosition(pos) === false

  const trimEmptyTextNode$1 = (dom, node) => {
    if (isText$9(node) && node.data.length === 0) {
      dom.remove(node)
    }
  }
  const insertNode = (dom, rng, node) => {
    rng.insertNode(node)
    trimEmptyTextNode$1(dom, node.previousSibling)
    trimEmptyTextNode$1(dom, node.nextSibling)
  }
  const insertFragment = (dom, rng, frag) => {
    const firstChild = Optional.from(frag.firstChild)
    const lastChild = Optional.from(frag.lastChild)
    rng.insertNode(frag)
    firstChild.each((child) => trimEmptyTextNode$1(dom, child.previousSibling))
    lastChild.each((child) => trimEmptyTextNode$1(dom, child.nextSibling))
  }
  const rangeInsertNode = (dom, rng, node) => {
    if (isDocumentFragment(node)) {
      insertFragment(dom, rng, node)
    } else {
      insertNode(dom, rng, node)
    }
  }

  const isText$5 = isText$9
  const isBogus = isBogus$2
  const nodeIndex = DOMUtils.nodeIndex
  const normalizedParent = (node) => {
    const parentNode = node.parentNode
    if (isBogus(parentNode)) {
      return normalizedParent(parentNode)
    }
    return parentNode
  }
  const getChildNodes = (node) => {
    if (!node) {
      return []
    }
    return reduce(
      node.childNodes,
      (result, node) => {
        if (isBogus(node) && node.nodeName !== 'BR') {
          result = result.concat(getChildNodes(node))
        } else {
          result.push(node)
        }
        return result
      },
      []
    )
  }
  const normalizedTextOffset = (node, offset) => {
    while ((node = node.previousSibling)) {
      if (!isText$5(node)) {
        break
      }
      offset += node.data.length
    }
    return offset
  }
  const equal = (a) => (b) => a === b
  const normalizedNodeIndex = (node) => {
    let nodes, index
    nodes = getChildNodes(normalizedParent(node))
    index = findIndex$1(nodes, equal(node), node)
    nodes = nodes.slice(0, index + 1)
    const numTextFragments = reduce(
      nodes,
      (result, node, i) => {
        if (isText$5(node) && isText$5(nodes[i - 1])) {
          result++
        }
        return result
      },
      0
    )
    nodes = filter$4(nodes, matchNodeNames([node.nodeName]))
    index = findIndex$1(nodes, equal(node), node)
    return index - numTextFragments
  }
  const createPathItem = (node) => {
    let name
    if (isText$5(node)) {
      name = 'text()'
    } else {
      name = node.nodeName.toLowerCase()
    }
    return name + '[' + normalizedNodeIndex(node) + ']'
  }
  const parentsUntil$1 = (root, node, predicate) => {
    const parents = []
    for (node = node.parentNode; node !== root; node = node.parentNode) {
      if (predicate && predicate(node)) {
        break
      }
      parents.push(node)
    }
    return parents
  }
  const create$b = (root, caretPosition) => {
    let container,
      offset,
      path = [],
      outputOffset,
      childNodes,
      parents
    container = caretPosition.container()
    offset = caretPosition.offset()
    if (isText$5(container)) {
      outputOffset = normalizedTextOffset(container, offset)
    } else {
      childNodes = container.childNodes
      if (offset >= childNodes.length) {
        outputOffset = 'after'
        offset = childNodes.length - 1
      } else {
        outputOffset = 'before'
      }
      container = childNodes[offset]
    }
    path.push(createPathItem(container))
    parents = parentsUntil$1(root, container)
    parents = filter$4(parents, not(isBogus$2))
    path = path.concat(
      map$1(parents, (node) => {
        return createPathItem(node)
      })
    )
    return path.reverse().join('/') + ',' + outputOffset
  }
  const resolvePathItem = (node, name, index) => {
    let nodes = getChildNodes(node)
    nodes = filter$4(nodes, (node, index) => {
      return !isText$5(node) || !isText$5(nodes[index - 1])
    })
    nodes = filter$4(nodes, matchNodeNames([name]))
    return nodes[index]
  }
  const findTextPosition = (container, offset) => {
    let node = container,
      targetOffset = 0,
      dataLen
    while (isText$5(node)) {
      dataLen = node.data.length
      if (offset >= targetOffset && offset <= targetOffset + dataLen) {
        container = node
        offset = offset - targetOffset
        break
      }
      if (!isText$5(node.nextSibling)) {
        container = node
        offset = dataLen
        break
      }
      targetOffset += dataLen
      node = node.nextSibling
    }
    if (isText$5(container) && offset > container.data.length) {
      offset = container.data.length
    }
    return CaretPosition(container, offset)
  }
  const resolve$1 = (root, path) => {
    let offset
    if (!path) {
      return null
    }
    const parts = path.split(',')
    const paths = parts[0].split('/')
    offset = parts.length > 1 ? parts[1] : 'before'
    const container = reduce(
      paths,
      (result, value) => {
        const match = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value)
        if (!match) {
          return null
        }
        if (match[1] === 'text()') {
          match[1] = '#text'
        }
        return resolvePathItem(result, match[1], parseInt(match[2], 10))
      },
      root
    )
    if (!container) {
      return null
    }
    if (!isText$5(container)) {
      if (offset === 'after') {
        offset = nodeIndex(container) + 1
      } else {
        offset = nodeIndex(container)
      }
      return CaretPosition(container.parentNode, offset)
    }
    return findTextPosition(container, parseInt(offset, 10))
  }

  const isContentEditableFalse$9 = isContentEditableFalse$b
  const getNormalizedTextOffset = (trim, container, offset) => {
    let node, trimmedOffset
    trimmedOffset = trim(container.data.slice(0, offset)).length
    for (node = container.previousSibling; node && isText$9(node); node = node.previousSibling) {
      trimmedOffset += trim(node.data).length
    }
    return trimmedOffset
  }
  const getPoint = (dom, trim, normalized, rng, start) => {
    let container = rng[start ? 'startContainer' : 'endContainer']
    let offset = rng[start ? 'startOffset' : 'endOffset']
    const point = []
    let childNodes,
      after = 0
    const root = dom.getRoot()
    if (isText$9(container)) {
      point.push(normalized ? getNormalizedTextOffset(trim, container, offset) : offset)
    } else {
      childNodes = container.childNodes
      if (offset >= childNodes.length && childNodes.length) {
        after = 1
        offset = Math.max(0, childNodes.length - 1)
      }
      point.push(dom.nodeIndex(childNodes[offset], normalized) + after)
    }
    for (; container && container !== root; container = container.parentNode) {
      point.push(dom.nodeIndex(container, normalized))
    }
    return point
  }
  const getLocation = (trim, selection, normalized, rng) => {
    const dom = selection.dom
    const start = getPoint(dom, trim, normalized, rng, true)
    const forward = selection.isForward()
    const fakeCaret = isRangeInCaretContainerBlock(rng) ? { isFakeCaret: true } : {}
    if (!selection.isCollapsed()) {
      const end = getPoint(dom, trim, normalized, rng, false)
      return {
        start,
        end,
        forward,
        ...fakeCaret,
      }
    } else {
      return {
        start,
        forward,
        ...fakeCaret,
      }
    }
  }
  const findIndex = (dom, name, element) => {
    let count = 0
    Tools.each(dom.select(name), (node) => {
      if (node.getAttribute('data-mce-bogus') === 'all') {
        return
      }
      if (node === element) {
        return false
      }
      count++
    })
    return count
  }
  const moveEndPoint$1 = (rng, start) => {
    let container, offset
    const prefix = start ? 'start' : 'end'
    container = rng[prefix + 'Container']
    offset = rng[prefix + 'Offset']
    if (isElement$6(container) && container.nodeName === 'TR') {
      const childNodes = container.childNodes
      container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]
      if (container) {
        offset = start ? 0 : container.childNodes.length
        rng['set' + (start ? 'Start' : 'End')](container, offset)
      }
    }
  }
  const normalizeTableCellSelection = (rng) => {
    moveEndPoint$1(rng, true)
    moveEndPoint$1(rng, false)
    return rng
  }
  const findSibling = (node, offset) => {
    let sibling
    if (isElement$6(node)) {
      node = getNode$1(node, offset)
      if (isContentEditableFalse$9(node)) {
        return node
      }
    }
    if (isCaretContainer$2(node)) {
      if (isText$9(node) && isCaretContainerBlock$1(node)) {
        node = node.parentNode
      }
      sibling = node.previousSibling
      if (isContentEditableFalse$9(sibling)) {
        return sibling
      }
      sibling = node.nextSibling
      if (isContentEditableFalse$9(sibling)) {
        return sibling
      }
    }
  }
  const findAdjacentContentEditableFalseElm = (rng) => {
    return findSibling(rng.startContainer, rng.startOffset) || findSibling(rng.endContainer, rng.endOffset)
  }
  const getOffsetBookmark = (trim, normalized, selection) => {
    const element = selection.getNode()
    let name = element ? element.nodeName : null
    const rng = selection.getRng()
    if (isContentEditableFalse$9(element) || name === 'IMG') {
      return {
        name,
        index: findIndex(selection.dom, name, element),
      }
    }
    const sibling = findAdjacentContentEditableFalseElm(rng)
    if (sibling) {
      name = sibling.tagName
      return {
        name,
        index: findIndex(selection.dom, name, sibling),
      }
    }
    return getLocation(trim, selection, normalized, rng)
  }
  const getCaretBookmark = (selection) => {
    const rng = selection.getRng()
    return {
      start: create$b(selection.dom.getRoot(), CaretPosition.fromRangeStart(rng)),
      end: create$b(selection.dom.getRoot(), CaretPosition.fromRangeEnd(rng)),
      forward: selection.isForward(),
    }
  }
  const getRangeBookmark = (selection) => {
    return {
      rng: selection.getRng(),
      forward: selection.isForward(),
    }
  }
  const createBookmarkSpan = (dom, id, filled) => {
    const args = {
      'data-mce-type': 'bookmark',
      id,
      'style': 'overflow:hidden;line-height:0px',
    }
    return filled ? dom.create('span', args, '&#xFEFF;') : dom.create('span', args)
  }
  const getPersistentBookmark = (selection, filled) => {
    const dom = selection.dom
    let rng = selection.getRng()
    const id = dom.uniqueId()
    const collapsed = selection.isCollapsed()
    const element = selection.getNode()
    const name = element.nodeName
    const forward = selection.isForward()
    if (name === 'IMG') {
      return {
        name,
        index: findIndex(dom, name, element),
      }
    }
    const rng2 = normalizeTableCellSelection(rng.cloneRange())
    if (!collapsed) {
      rng2.collapse(false)
      const endBookmarkNode = createBookmarkSpan(dom, id + '_end', filled)
      rangeInsertNode(dom, rng2, endBookmarkNode)
    }
    rng = normalizeTableCellSelection(rng)
    rng.collapse(true)
    const startBookmarkNode = createBookmarkSpan(dom, id + '_start', filled)
    rangeInsertNode(dom, rng, startBookmarkNode)
    selection.moveToBookmark({
      id,
      keep: true,
      forward,
    })
    return {
      id,
      forward,
    }
  }
  const getBookmark$2 = (selection, type, normalized) => {
    if (type === 2) {
      return getOffsetBookmark(trim$1, normalized, selection)
    } else if (type === 3) {
      return getCaretBookmark(selection)
    } else if (type) {
      return getRangeBookmark(selection)
    } else {
      return getPersistentBookmark(selection, false)
    }
  }
  const getUndoBookmark = curry(getOffsetBookmark, identity, true)

  const value$1 = (value) => {
    const applyHelper = (fn) => fn(value)
    const constHelper = constant(value)
    const outputHelper = () => output
    const output = {
      tag: true,
      inner: value,
      fold: (_onError, onValue) => onValue(value),
      isValue: always,
      isError: never,
      map: (mapper) => Result.value(mapper(value)),
      mapError: outputHelper,
      bind: applyHelper,
      exists: applyHelper,
      forall: applyHelper,
      getOr: constHelper,
      or: outputHelper,
      getOrThunk: constHelper,
      orThunk: outputHelper,
      getOrDie: constHelper,
      each: (fn) => {
        fn(value)
      },
      toOptional: () => Optional.some(value),
    }
    return output
  }
  const error = (error) => {
    const outputHelper = () => output
    const output = {
      tag: false,
      inner: error,
      fold: (onError, _onValue) => onError(error),
      isValue: never,
      isError: always,
      map: outputHelper,
      mapError: (mapper) => Result.error(mapper(error)),
      bind: outputHelper,
      exists: never,
      forall: always,
      getOr: identity,
      or: identity,
      getOrThunk: apply$1,
      orThunk: apply$1,
      getOrDie: die(String(error)),
      each: noop,
      toOptional: Optional.none,
    }
    return output
  }
  const fromOption = (optional, err) => optional.fold(() => error(err), value$1)
  const Result = {
    value: value$1,
    error,
    fromOption,
  }

  const generate = (cases) => {
    if (!isArray$1(cases)) {
      throw new Error('cases must be an array')
    }
    if (cases.length === 0) {
      throw new Error('there must be at least one case')
    }
    const constructors = []
    const adt = {}
    each$f(cases, (acase, count) => {
      const keys$1 = keys(acase)
      if (keys$1.length !== 1) {
        throw new Error('one and only one name per case')
      }
      const key = keys$1[0]
      const value = acase[key]
      if (adt[key] !== undefined) {
        throw new Error('duplicate key detected:' + key)
      } else if (key === 'cata') {
        throw new Error('cannot have a case named cata (sorry)')
      } else if (!isArray$1(value)) {
        throw new Error('case arguments must be an array')
      }
      constructors.push(key)
      adt[key] = (...args) => {
        const argLength = args.length
        if (argLength !== value.length) {
          throw new Error(
            'Wrong number of arguments to case ' +
              key +
              '. Expected ' +
              value.length +
              ' (' +
              value +
              '), got ' +
              argLength
          )
        }
        const match = (branches) => {
          const branchKeys = keys(branches)
          if (constructors.length !== branchKeys.length) {
            throw new Error(
              'Wrong number of arguments to match. Expected: ' +
                constructors.join(',') +
                '\nActual: ' +
                branchKeys.join(',')
            )
          }
          const allReqd = forall(constructors, (reqKey) => {
            return contains$2(branchKeys, reqKey)
          })
          if (!allReqd) {
            throw new Error(
              'Not all branches were specified when using match. Specified: ' +
                branchKeys.join(', ') +
                '\nRequired: ' +
                constructors.join(', ')
            )
          }
          return branches[key].apply(null, args)
        }
        return {
          fold: (...foldArgs) => {
            if (foldArgs.length !== cases.length) {
              throw new Error(
                'Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + foldArgs.length
              )
            }
            const target = foldArgs[count]
            return target.apply(null, args)
          },
          match,
          log: (label) => {
            console.log(label, {
              constructors,
              constructor: key,
              params: args,
            })
          },
        }
      }
    })
    return adt
  }
  const Adt = { generate }

  Adt.generate([
    {
      bothErrors: ['error1', 'error2'],
    },
    {
      firstError: ['error1', 'value2'],
    },
    {
      secondError: ['value1', 'error2'],
    },
    {
      bothValues: ['value1', 'value2'],
    },
  ])
  const partition$1 = (results) => {
    const errors = []
    const values = []
    each$f(results, (result) => {
      result.fold(
        (err) => {
          errors.push(err)
        },
        (value) => {
          values.push(value)
        }
      )
    })
    return {
      errors,
      values,
    }
  }

  const isInlinePattern = (pattern) => pattern.type === 'inline-command' || pattern.type === 'inline-format'
  const isBlockPattern = (pattern) => pattern.type === 'block-command' || pattern.type === 'block-format'
  const sortPatterns = (patterns) =>
    sort(patterns, (a, b) => {
      if (a.start.length === b.start.length) {
        return 0
      }
      return a.start.length > b.start.length ? -1 : 1
    })
  const normalizePattern = (pattern) => {
    const err = (message) =>
      Result.error({
        message,
        pattern,
      })
    const formatOrCmd = (name, onFormat, onCommand) => {
      if (pattern.format !== undefined) {
        let formats
        if (isArray$1(pattern.format)) {
          if (!forall(pattern.format, isString)) {
            return err(name + ' pattern has non-string items in the `format` array')
          }
          formats = pattern.format
        } else if (isString(pattern.format)) {
          formats = [pattern.format]
        } else {
          return err(name + ' pattern has non-string `format` parameter')
        }
        return Result.value(onFormat(formats))
      } else if (pattern.cmd !== undefined) {
        if (!isString(pattern.cmd)) {
          return err(name + ' pattern has non-string `cmd` parameter')
        }
        return Result.value(onCommand(pattern.cmd, pattern.value))
      } else {
        return err(name + ' pattern is missing both `format` and `cmd` parameters')
      }
    }
    if (!isObject(pattern)) {
      return err('Raw pattern is not an object')
    }
    if (!isString(pattern.start)) {
      return err('Raw pattern is missing `start` parameter')
    }
    if (pattern.end !== undefined) {
      if (!isString(pattern.end)) {
        return err('Inline pattern has non-string `end` parameter')
      }
      if (pattern.start.length === 0 && pattern.end.length === 0) {
        return err('Inline pattern has empty `start` and `end` parameters')
      }
      let start = pattern.start
      let end = pattern.end
      if (end.length === 0) {
        end = start
        start = ''
      }
      return formatOrCmd(
        'Inline',
        (format) => ({
          type: 'inline-format',
          start,
          end,
          format,
        }),
        (cmd, value) => ({
          type: 'inline-command',
          start,
          end,
          cmd,
          value,
        })
      )
    } else if (pattern.replacement !== undefined) {
      if (!isString(pattern.replacement)) {
        return err('Replacement pattern has non-string `replacement` parameter')
      }
      if (pattern.start.length === 0) {
        return err('Replacement pattern has empty `start` parameter')
      }
      return Result.value({
        type: 'inline-command',
        start: '',
        end: pattern.start,
        cmd: 'mceInsertContent',
        value: pattern.replacement,
      })
    } else {
      if (pattern.start.length === 0) {
        return err('Block pattern has empty `start` parameter')
      }
      return formatOrCmd(
        'Block',
        (formats) => ({
          type: 'block-format',
          start: pattern.start,
          format: formats[0],
        }),
        (command, commandValue) => ({
          type: 'block-command',
          start: pattern.start,
          cmd: command,
          value: commandValue,
        })
      )
    }
  }
  const getBlockPatterns = (patterns) => sortPatterns(filter$6(patterns, isBlockPattern))
  const getInlinePatterns = (patterns) => filter$6(patterns, isInlinePattern)
  const createPatternSet = (patterns) => ({
    inlinePatterns: getInlinePatterns(patterns),
    blockPatterns: getBlockPatterns(patterns),
  })
  const fromRawPatterns = (patterns) => {
    const normalized = partition$1(map$3(patterns, normalizePattern))
    each$f(normalized.errors, (err) => console.error(err.message, err.pattern))
    return normalized.values
  }

  const deviceDetection$1 = detect$2().deviceType
  const isTouch = deviceDetection$1.isTouch()
  const DOM$a = DOMUtils.DOM
  const getHash = (value) => {
    const items = value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(',')
    return foldl(
      items,
      (output, item) => {
        const arr = item.split('=')
        const key = arr[0]
        const val = arr.length > 1 ? arr[1] : key
        output[trim$3(key)] = trim$3(val)
        return output
      },
      {}
    )
  }
  const isRegExp = (x) => is$4(x, RegExp)
  const option = (name) => (editor) => editor.options.get(name)
  const stringOrObjectProcessor = (value) => isString(value) || isObject(value)
  const bodyOptionProcessor =
    (editor, defaultValue = '') =>
    (value) => {
      const valid = isString(value)
      if (valid) {
        if (value.indexOf('=') !== -1) {
          const bodyObj = getHash(value)
          return {
            value: get$a(bodyObj, editor.id).getOr(defaultValue),
            valid,
          }
        } else {
          return {
            value,
            valid,
          }
        }
      } else {
        return {
          valid: false,
          message: 'Must be a string.',
        }
      }
    }
  const register$7 = (editor) => {
    const registerOption = editor.options.register
    registerOption('id', {
      processor: 'string',
      default: editor.id,
    })
    registerOption('selector', { processor: 'string' })
    registerOption('target', { processor: 'object' })
    registerOption('suffix', { processor: 'string' })
    registerOption('cache_suffix', { processor: 'string' })
    registerOption('base_url', { processor: 'string' })
    registerOption('referrer_policy', {
      processor: 'string',
      default: '',
    })
    registerOption('language_load', { processor: 'boolean' })
    registerOption('inline', {
      processor: 'boolean',
      default: false,
    })
    registerOption('iframe_attrs', {
      processor: 'object',
      default: {},
    })
    registerOption('doctype', {
      processor: 'string',
      default: '<!DOCTYPE html>',
    })
    registerOption('document_base_url', {
      processor: 'string',
      default: editor.documentBaseUrl,
    })
    registerOption('body_id', {
      processor: bodyOptionProcessor(editor, 'tinymce'),
      default: 'tinymce',
    })
    registerOption('body_class', {
      processor: bodyOptionProcessor(editor),
      default: '',
    })
    registerOption('content_security_policy', {
      processor: 'string',
      default: '',
    })
    registerOption('br_in_pre', {
      processor: 'boolean',
      default: true,
    })
    registerOption('forced_root_block', {
      processor: (value) => {
        const valid = isString(value) && isNotEmpty(value)
        if (valid) {
          return {
            value,
            valid,
          }
        } else {
          return {
            valid: false,
            message: 'Must be a non-empty string.',
          }
        }
      },
      default: 'p',
    })
    registerOption('forced_root_block_attrs', {
      processor: 'object',
      default: {},
    })
    registerOption('newline_behavior', {
      processor: (value) => {
        const valid = contains$2(['block', 'linebreak', 'invert', 'default'], value)
        return valid
          ? {
              value,
              valid,
            }
          : {
              valid: false,
              message: 'Must be one of: block, linebreak, invert or default.',
            }
      },
      default: 'default',
    })
    registerOption('br_newline_selector', {
      processor: 'string',
      default: '.mce-toc h2,figcaption,caption',
    })
    registerOption('no_newline_selector', {
      processor: 'string',
      default: '',
    })
    registerOption('keep_styles', {
      processor: 'boolean',
      default: true,
    })
    registerOption('end_container_on_empty_block', {
      processor: (value) => {
        if (isBoolean(value)) {
          return {
            valid: true,
            value,
          }
        } else if (isString(value)) {
          return {
            valid: true,
            value,
          }
        } else {
          return {
            valid: false,
            message: 'Must be boolean or a string',
          }
        }
      },
      default: 'blockquote',
    })
    registerOption('font_size_style_values', {
      processor: 'string',
      default: 'xx-small,x-small,small,medium,large,x-large,xx-large',
    })
    registerOption('font_size_legacy_values', {
      processor: 'string',
      default: 'xx-small,small,medium,large,x-large,xx-large,300%',
    })
    registerOption('font_size_classes', {
      processor: 'string',
      default: '',
    })
    registerOption('automatic_uploads', {
      processor: 'boolean',
      default: true,
    })
    registerOption('images_reuse_filename', {
      processor: 'boolean',
      default: false,
    })
    registerOption('images_replace_blob_uris', {
      processor: 'boolean',
      default: true,
    })
    registerOption('icons', {
      processor: 'string',
      default: '',
    })
    registerOption('icons_url', {
      processor: 'string',
      default: '',
    })
    registerOption('images_upload_url', {
      processor: 'string',
      default: '',
    })
    registerOption('images_upload_base_path', {
      processor: 'string',
      default: '',
    })
    registerOption('images_upload_base_path', {
      processor: 'string',
      default: '',
    })
    registerOption('images_upload_credentials', {
      processor: 'boolean',
      default: false,
    })
    registerOption('images_upload_handler', { processor: 'function' })
    registerOption('language', {
      processor: 'string',
      default: 'en',
    })
    registerOption('language_url', {
      processor: 'string',
      default: '',
    })
    registerOption('entity_encoding', {
      processor: 'string',
      default: 'named',
    })
    registerOption('indent', {
      processor: 'boolean',
      default: true,
    })
    registerOption('indent_before', {
      processor: 'string',
      default:
        'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
        'tfoot,tbody,tr,section,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
    })
    registerOption('indent_after', {
      processor: 'string',
      default:
        'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
        'tfoot,tbody,tr,section,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
    })
    registerOption('indent_use_margin', {
      processor: 'boolean',
      default: false,
    })
    registerOption('indentation', {
      processor: 'string',
      default: '40px',
    })
    registerOption('content_css', {
      processor: (value) => {
        const valid = value === false || isString(value) || isArrayOf(value, isString)
        if (valid) {
          if (isString(value)) {
            return {
              value: map$3(value.split(','), trim$3),
              valid,
            }
          } else if (isArray$1(value)) {
            return {
              value,
              valid,
            }
          } else if (value === false) {
            return {
              value: [],
              valid,
            }
          } else {
            return {
              value,
              valid,
            }
          }
        } else {
          return {
            valid: false,
            message: 'Must be false, a string or an array of strings.',
          }
        }
      },
      default: isInline(editor) ? [] : ['default'],
    })
    registerOption('content_style', { processor: 'string' })
    registerOption('content_css_cors', {
      processor: 'boolean',
      default: false,
    })
    registerOption('font_css', {
      processor: (value) => {
        const valid = isString(value) || isArrayOf(value, isString)
        if (valid) {
          const newValue = isArray$1(value) ? value : map$3(value.split(','), trim$3)
          return {
            value: newValue,
            valid,
          }
        } else {
          return {
            valid: false,
            message: 'Must be a string or an array of strings.',
          }
        }
      },
      default: [],
    })
    registerOption('inline_boundaries', {
      processor: 'boolean',
      default: true,
    })
    registerOption('inline_boundaries_selector', {
      processor: 'string',
      default: 'a[href],code,span.mce-annotation',
    })
    registerOption('object_resizing', {
      processor: (value) => {
        const valid = isBoolean(value) || isString(value)
        if (valid) {
          if (value === false || deviceDetection$1.isiPhone() || deviceDetection$1.isiPad()) {
            return {
              value: '',
              valid,
            }
          } else {
            return {
              value: value === true ? 'table,img,figure.image,div,video,iframe' : value,
              valid,
            }
          }
        } else {
          return {
            valid: false,
            message: 'Must be boolean or a string',
          }
        }
      },
      default: !isTouch,
    })
    registerOption('resize_img_proportional', {
      processor: 'boolean',
      default: true,
    })
    registerOption('event_root', { processor: 'object' })
    registerOption('service_message', { processor: 'string' })
    registerOption('theme', {
      processor: (value) => value === false || isString(value) || isFunction(value),
      default: 'silver',
    })
    registerOption('theme_url', { processor: 'string' })
    registerOption('formats', { processor: 'object' })
    registerOption('format_empty_lines', {
      processor: 'boolean',
      default: false,
    })
    registerOption('preview_styles', {
      processor: (value) => {
        const valid = value === false || isString(value)
        if (valid) {
          return {
            value: value === false ? '' : value,
            valid,
          }
        } else {
          return {
            valid: false,
            message: 'Must be false or a string',
          }
        }
      },
      default:
        'font-family font-size font-weight font-style text-decoration text-transform color background-color border border-radius outline text-shadow',
    })
    registerOption('custom_ui_selector', {
      processor: 'string',
      default: '',
    })
    registerOption('hidden_input', {
      processor: 'boolean',
      default: true,
    })
    registerOption('submit_patch', {
      processor: 'boolean',
      default: true,
    })
    registerOption('encoding', { processor: 'string' })
    registerOption('add_form_submit_trigger', {
      processor: 'boolean',
      default: true,
    })
    registerOption('add_unload_trigger', {
      processor: 'boolean',
      default: true,
    })
    registerOption('custom_undo_redo_levels', {
      processor: 'number',
      default: 0,
    })
    registerOption('disable_nodechange', {
      processor: 'boolean',
      default: false,
    })
    registerOption('readonly', {
      processor: 'boolean',
      default: false,
    })
    registerOption('plugins', {
      processor: 'string[]',
      default: [],
    })
    registerOption('external_plugins', { processor: 'object' })
    registerOption('forced_plugins', { processor: 'string[]' })
    registerOption('model', {
      processor: 'string',
      default: editor.hasPlugin('rtc') ? 'plugin' : 'dom',
    })
    registerOption('model_url', { processor: 'string' })
    registerOption('block_unsupported_drop', {
      processor: 'boolean',
      default: true,
    })
    registerOption('visual', {
      processor: 'boolean',
      default: true,
    })
    registerOption('visual_table_class', {
      processor: 'string',
      default: 'mce-item-table',
    })
    registerOption('visual_anchor_class', {
      processor: 'string',
      default: 'mce-item-anchor',
    })
    registerOption('iframe_aria_text', {
      processor: 'string',
      default: 'Rich Text Area. Press ALT-0 for help.',
    })
    registerOption('setup', { processor: 'function' })
    registerOption('init_instance_callback', { processor: 'function' })
    registerOption('url_converter', {
      processor: 'function',
      default: editor.convertURL,
    })
    registerOption('url_converter_scope', {
      processor: 'object',
      default: editor,
    })
    registerOption('urlconverter_callback', { processor: 'function' })
    registerOption('allow_conditional_comments', {
      processor: 'boolean',
      default: false,
    })
    registerOption('allow_html_data_urls', {
      processor: 'boolean',
      default: false,
    })
    registerOption('allow_svg_data_urls', { processor: 'boolean' })
    registerOption('allow_html_in_named_anchor', {
      processor: 'boolean',
      default: false,
    })
    registerOption('allow_script_urls', {
      processor: 'boolean',
      default: false,
    })
    registerOption('allow_unsafe_link_target', {
      processor: 'boolean',
      default: false,
    })
    registerOption('convert_fonts_to_spans', {
      processor: 'boolean',
      default: true,
      deprecated: true,
    })
    registerOption('fix_list_elements', {
      processor: 'boolean',
      default: false,
    })
    registerOption('preserve_cdata', {
      processor: 'boolean',
      default: false,
    })
    registerOption('remove_trailing_brs', { processor: 'boolean' })
    registerOption('inline_styles', {
      processor: 'boolean',
      default: true,
      deprecated: true,
    })
    registerOption('element_format', {
      processor: 'string',
      default: 'html',
    })
    registerOption('entities', { processor: 'string' })
    registerOption('schema', {
      processor: 'string',
      default: 'html5',
    })
    registerOption('convert_urls', {
      processor: 'boolean',
      default: true,
    })
    registerOption('relative_urls', {
      processor: 'boolean',
      default: true,
    })
    registerOption('remove_script_host', {
      processor: 'boolean',
      default: true,
    })
    registerOption('custom_elements', { processor: 'string' })
    registerOption('extended_valid_elements', { processor: 'string' })
    registerOption('invalid_elements', { processor: 'string' })
    registerOption('invalid_styles', { processor: stringOrObjectProcessor })
    registerOption('valid_children', { processor: 'string' })
    registerOption('valid_classes', { processor: stringOrObjectProcessor })
    registerOption('valid_elements', { processor: 'string' })
    registerOption('valid_styles', { processor: stringOrObjectProcessor })
    registerOption('verify_html', {
      processor: 'boolean',
      default: true,
    })
    registerOption('auto_focus', { processor: (value) => isString(value) || value === true })
    registerOption('browser_spellcheck', {
      processor: 'boolean',
      default: false,
    })
    registerOption('protect', { processor: 'array' })
    registerOption('images_file_types', {
      processor: 'string',
      default: 'jpeg,jpg,jpe,jfi,jif,jfif,png,gif,bmp,webp',
    })
    registerOption('deprecation_warnings', {
      processor: 'boolean',
      default: true,
    })
    registerOption('a11y_advanced_options', {
      processor: 'boolean',
      default: false,
    })
    registerOption('api_key', { processor: 'string' })
    registerOption('paste_block_drop', {
      processor: 'boolean',
      default: false,
    })
    registerOption('paste_data_images', {
      processor: 'boolean',
      default: true,
    })
    registerOption('paste_preprocess', { processor: 'function' })
    registerOption('paste_postprocess', { processor: 'function' })
    registerOption('paste_webkit_styles', {
      processor: 'string',
      default: 'none',
    })
    registerOption('paste_remove_styles_if_webkit', {
      processor: 'boolean',
      default: true,
    })
    registerOption('paste_merge_formats', {
      processor: 'boolean',
      default: true,
    })
    registerOption('smart_paste', {
      processor: 'boolean',
      default: true,
    })
    registerOption('paste_as_text', {
      processor: 'boolean',
      default: false,
    })
    registerOption('paste_tab_spaces', {
      processor: 'number',
      default: 4,
    })
    registerOption('text_patterns', {
      processor: (value) => {
        if (isArrayOf(value, isObject) || value === false) {
          const patterns = value === false ? [] : value
          return {
            value: fromRawPatterns(patterns),
            valid: true,
          }
        } else {
          return {
            valid: false,
            message: 'Must be an array of objects or false.',
          }
        }
      },
      default: [
        {
          start: '*',
          end: '*',
          format: 'italic',
        },
        {
          start: '**',
          end: '**',
          format: 'bold',
        },
        {
          start: '#',
          format: 'h1',
        },
        {
          start: '##',
          format: 'h2',
        },
        {
          start: '###',
          format: 'h3',
        },
        {
          start: '####',
          format: 'h4',
        },
        {
          start: '#####',
          format: 'h5',
        },
        {
          start: '######',
          format: 'h6',
        },
        {
          start: '1. ',
          cmd: 'InsertOrderedList',
        },
        {
          start: '* ',
          cmd: 'InsertUnorderedList',
        },
        {
          start: '- ',
          cmd: 'InsertUnorderedList',
        },
      ],
    })
    registerOption('noneditable_class', {
      processor: 'string',
      default: 'mceNonEditable',
    })
    registerOption('editable_class', {
      processor: 'string',
      default: 'mceEditable',
    })
    registerOption('noneditable_regexp', {
      processor: (value) => {
        if (isArrayOf(value, isRegExp)) {
          return {
            value,
            valid: true,
          }
        } else if (isRegExp(value)) {
          return {
            value: [value],
            valid: true,
          }
        } else {
          return {
            valid: false,
            message: 'Must be a RegExp or an array of RegExp.',
          }
        }
      },
      default: [],
    })
    registerOption('table_tab_navigation', {
      processor: 'boolean',
      default: true,
    })
    editor.on('ScriptsLoaded', () => {
      registerOption('directionality', {
        processor: 'string',
        default: I18n.isRtl() ? 'rtl' : undefined,
      })
      registerOption('placeholder', {
        processor: 'string',
        default: DOM$a.getAttrib(editor.getElement(), 'placeholder'),
      })
    })
  }
  const getIframeAttrs = option('iframe_attrs')
  const getDocType = option('doctype')
  const getDocumentBaseUrl = option('document_base_url')
  const getBodyId = option('body_id')
  const getBodyClass = option('body_class')
  const getContentSecurityPolicy = option('content_security_policy')
  const shouldPutBrInPre$1 = option('br_in_pre')
  const getForcedRootBlock = option('forced_root_block')
  const getForcedRootBlockAttrs = option('forced_root_block_attrs')
  const getNewlineBehavior = option('newline_behavior')
  const getBrNewLineSelector = option('br_newline_selector')
  const getNoNewLineSelector = option('no_newline_selector')
  const shouldKeepStyles = option('keep_styles')
  const shouldEndContainerOnEmptyBlock = option('end_container_on_empty_block')
  const isAutomaticUploadsEnabled = option('automatic_uploads')
  const shouldReuseFileName = option('images_reuse_filename')
  const shouldReplaceBlobUris = option('images_replace_blob_uris')
  const getIconPackName = option('icons')
  const getIconsUrl = option('icons_url')
  const getImageUploadUrl = option('images_upload_url')
  const getImageUploadBasePath = option('images_upload_base_path')
  const getImagesUploadCredentials = option('images_upload_credentials')
  const getImagesUploadHandler = option('images_upload_handler')
  const shouldUseContentCssCors = option('content_css_cors')
  const getReferrerPolicy = option('referrer_policy')
  const getLanguageCode = option('language')
  const getLanguageUrl = option('language_url')
  const shouldIndentUseMargin = option('indent_use_margin')
  const getIndentation = option('indentation')
  const getContentCss = option('content_css')
  const getContentStyle = option('content_style')
  const getFontCss = option('font_css')
  const getDirectionality = option('directionality')
  const getInlineBoundarySelector = option('inline_boundaries_selector')
  const getObjectResizing = option('object_resizing')
  const getResizeImgProportional = option('resize_img_proportional')
  const getPlaceholder = option('placeholder')
  const getEventRoot = option('event_root')
  const getServiceMessage = option('service_message')
  const getTheme = option('theme')
  const getThemeUrl = option('theme_url')
  const getModel = option('model')
  const getModelUrl = option('model_url')
  const isInlineBoundariesEnabled = option('inline_boundaries')
  const getFormats = option('formats')
  const getPreviewStyles = option('preview_styles')
  const canFormatEmptyLines = option('format_empty_lines')
  const getCustomUiSelector = option('custom_ui_selector')
  const isInline = option('inline')
  const hasHiddenInput = option('hidden_input')
  const shouldPatchSubmit = option('submit_patch')
  const shouldAddFormSubmitTrigger = option('add_form_submit_trigger')
  const shouldAddUnloadTrigger = option('add_unload_trigger')
  const getCustomUndoRedoLevels = option('custom_undo_redo_levels')
  const shouldDisableNodeChange = option('disable_nodechange')
  const isReadOnly$1 = option('readonly')
  const hasContentCssCors = option('content_css_cors')
  const getPlugins = option('plugins')
  const getExternalPlugins$1 = option('external_plugins')
  const shouldBlockUnsupportedDrop = option('block_unsupported_drop')
  const isVisualAidsEnabled = option('visual')
  const getVisualAidsTableClass = option('visual_table_class')
  const getVisualAidsAnchorClass = option('visual_anchor_class')
  const getIframeAriaText = option('iframe_aria_text')
  const getSetupCallback = option('setup')
  const getInitInstanceCallback = option('init_instance_callback')
  const getUrlConverterCallback = option('urlconverter_callback')
  const getAutoFocus = option('auto_focus')
  const shouldBrowserSpellcheck = option('browser_spellcheck')
  const getProtect = option('protect')
  const shouldPasteBlockDrop = option('paste_block_drop')
  const shouldPasteDataImages = option('paste_data_images')
  const getPastePreProcess = option('paste_preprocess')
  const getPastePostProcess = option('paste_postprocess')
  const getPasteWebkitStyles = option('paste_webkit_styles')
  const shouldPasteRemoveWebKitStyles = option('paste_remove_styles_if_webkit')
  const shouldPasteMergeFormats = option('paste_merge_formats')
  const isSmartPasteEnabled = option('smart_paste')
  const isPasteAsTextEnabled = option('paste_as_text')
  const getPasteTabSpaces = option('paste_tab_spaces')
  const shouldAllowHtmlDataUrls = option('allow_html_data_urls')
  const getTextPatterns = option('text_patterns')
  const getNonEditableClass = option('noneditable_class')
  const getEditableClass = option('editable_class')
  const getNonEditableRegExps = option('noneditable_regexp')
  const getFontStyleValues = (editor) => Tools.explode(editor.options.get('font_size_style_values'))
  const getFontSizeClasses = (editor) => Tools.explode(editor.options.get('font_size_classes'))
  const isEncodingXml = (editor) => editor.options.get('encoding') === 'xml'
  const getAllowedImageFileTypes = (editor) => Tools.explode(editor.options.get('images_file_types'))
  const hasTableTabNavigation = option('table_tab_navigation')

  const isElement$3 = isElement$6
  const isText$4 = isText$9
  const removeNode$1 = (node) => {
    const parentNode = node.parentNode
    if (parentNode) {
      parentNode.removeChild(node)
    }
  }
  const trimCount = (text) => {
    const trimmedText = trim$1(text)
    return {
      count: text.length - trimmedText.length,
      text: trimmedText,
    }
  }
  const deleteZwspChars = (caretContainer) => {
    let idx
    while ((idx = caretContainer.data.lastIndexOf(ZWSP$1)) !== -1) {
      caretContainer.deleteData(idx, 1)
    }
  }
  const removeUnchanged = (caretContainer, pos) => {
    remove$4(caretContainer)
    return pos
  }
  const removeTextAndReposition = (caretContainer, pos) => {
    const before = trimCount(caretContainer.data.substr(0, pos.offset()))
    const after = trimCount(caretContainer.data.substr(pos.offset()))
    const text = before.text + after.text
    if (text.length > 0) {
      deleteZwspChars(caretContainer)
      return CaretPosition(caretContainer, pos.offset() - before.count)
    } else {
      return pos
    }
  }
  const removeElementAndReposition = (caretContainer, pos) => {
    const parentNode = pos.container()
    const newPosition = indexOf$1(from(parentNode.childNodes), caretContainer)
      .map((index) => {
        return index < pos.offset() ? CaretPosition(parentNode, pos.offset() - 1) : pos
      })
      .getOr(pos)
    remove$4(caretContainer)
    return newPosition
  }
  const removeTextCaretContainer = (caretContainer, pos) =>
    isText$4(caretContainer) && pos.container() === caretContainer
      ? removeTextAndReposition(caretContainer, pos)
      : removeUnchanged(caretContainer, pos)
  const removeElementCaretContainer = (caretContainer, pos) =>
    pos.container() === caretContainer.parentNode
      ? removeElementAndReposition(caretContainer, pos)
      : removeUnchanged(caretContainer, pos)
  const removeAndReposition = (container, pos) =>
    CaretPosition.isTextPosition(pos)
      ? removeTextCaretContainer(container, pos)
      : removeElementCaretContainer(container, pos)
  const remove$4 = (caretContainerNode) => {
    if (isElement$3(caretContainerNode) && isCaretContainer$2(caretContainerNode)) {
      if (hasContent(caretContainerNode)) {
        caretContainerNode.removeAttribute('data-mce-caret')
      } else {
        removeNode$1(caretContainerNode)
      }
    }
    if (isText$4(caretContainerNode)) {
      deleteZwspChars(caretContainerNode)
      if (caretContainerNode.data.length === 0) {
        removeNode$1(caretContainerNode)
      }
    }
  }

  const isContentEditableFalse$8 = isContentEditableFalse$b
  const isMedia$1 = isMedia$2
  const isTableCell$3 = isTableCell$5
  const inlineFakeCaretSelector = '*[contentEditable=false],video,audio,embed,object'
  const getAbsoluteClientRect = (root, element, before) => {
    const clientRect = collapse(element.getBoundingClientRect(), before)
    let scrollX
    let scrollY
    if (root.tagName === 'BODY') {
      const docElm = root.ownerDocument.documentElement
      scrollX = root.scrollLeft || docElm.scrollLeft
      scrollY = root.scrollTop || docElm.scrollTop
    } else {
      const rootRect = root.getBoundingClientRect()
      scrollX = root.scrollLeft - rootRect.left
      scrollY = root.scrollTop - rootRect.top
    }
    clientRect.left += scrollX
    clientRect.right += scrollX
    clientRect.top += scrollY
    clientRect.bottom += scrollY
    clientRect.width = 1
    let margin = element.offsetWidth - element.clientWidth
    if (margin > 0) {
      if (before) {
        margin *= -1
      }
      clientRect.left += margin
      clientRect.right += margin
    }
    return clientRect
  }
  const trimInlineCaretContainers = (root) => {
    const fakeCaretTargetNodes = descendants(SugarElement.fromDom(root), inlineFakeCaretSelector)
    for (let i = 0; i < fakeCaretTargetNodes.length; i++) {
      const node = fakeCaretTargetNodes[i].dom
      let sibling = node.previousSibling
      if (endsWithCaretContainer$1(sibling)) {
        const data = sibling.data
        if (data.length === 1) {
          sibling.parentNode.removeChild(sibling)
        } else {
          sibling.deleteData(data.length - 1, 1)
        }
      }
      sibling = node.nextSibling
      if (startsWithCaretContainer$1(sibling)) {
        const data = sibling.data
        if (data.length === 1) {
          sibling.parentNode.removeChild(sibling)
        } else {
          sibling.deleteData(0, 1)
        }
      }
    }
  }
  const FakeCaret = (editor, root, isBlock, hasFocus) => {
    const lastVisualCaret = value$2()
    let cursorInterval
    let caretContainerNode
    const caretBlock = getForcedRootBlock(editor)
    const dom = editor.dom
    const show = (before, element) => {
      let rng
      hide()
      if (isTableCell$3(element)) {
        return null
      }
      if (isBlock(element)) {
        caretContainerNode = insertBlock(caretBlock, element, before)
        const clientRect = getAbsoluteClientRect(root, element, before)
        dom.setStyle(caretContainerNode, 'top', clientRect.top)
        const caret = dom.create('div', {
          'class': 'mce-visual-caret',
          'data-mce-bogus': 'all',
        })
        dom.setStyles(caret, { ...clientRect })
        dom.add(root, caret)
        lastVisualCaret.set({
          caret,
          element,
          before,
        })
        if (before) {
          dom.addClass(caret, 'mce-visual-caret-before')
        }
        startBlink()
        rng = element.ownerDocument.createRange()
        rng.setStart(caretContainerNode, 0)
        rng.setEnd(caretContainerNode, 0)
      } else {
        caretContainerNode = insertInline$1(element, before)
        rng = element.ownerDocument.createRange()
        if (isInlineFakeCaretTarget(caretContainerNode.nextSibling)) {
          rng.setStart(caretContainerNode, 0)
          rng.setEnd(caretContainerNode, 0)
        } else {
          rng.setStart(caretContainerNode, 1)
          rng.setEnd(caretContainerNode, 1)
        }
        return rng
      }
      return rng
    }
    const hide = () => {
      trimInlineCaretContainers(root)
      if (caretContainerNode) {
        remove$4(caretContainerNode)
        caretContainerNode = null
      }
      lastVisualCaret.on((caretState) => {
        dom.remove(caretState.caret)
        lastVisualCaret.clear()
      })
      if (cursorInterval) {
        clearInterval(cursorInterval)
        cursorInterval = undefined
      }
    }
    const startBlink = () => {
      cursorInterval = setInterval(() => {
        lastVisualCaret.on((caretState) => {
          if (hasFocus()) {
            dom.toggleClass(caretState.caret, 'mce-visual-caret-hidden')
          } else {
            dom.addClass(caretState.caret, 'mce-visual-caret-hidden')
          }
        })
      }, 500)
    }
    const reposition = () => {
      lastVisualCaret.on((caretState) => {
        const clientRect = getAbsoluteClientRect(root, caretState.element, caretState.before)
        dom.setStyles(caretState.caret, { ...clientRect })
      })
    }
    const destroy = () => clearInterval(cursorInterval)
    const getCss = () =>
      '.mce-visual-caret {' +
      'position: absolute;' +
      'background-color: black;' +
      'background-color: currentcolor;' +
      '}' +
      '.mce-visual-caret-hidden {' +
      'display: none;' +
      '}' +
      '*[data-mce-caret] {' +
      'position: absolute;' +
      'left: -1000px;' +
      'right: auto;' +
      'top: 0;' +
      'margin: 0;' +
      'padding: 0;' +
      '}'
    return {
      show,
      hide,
      getCss,
      reposition,
      destroy,
    }
  }
  const isFakeCaretTableBrowser = () => Env.browser.isFirefox()
  const isInlineFakeCaretTarget = (node) => isContentEditableFalse$8(node) || isMedia$1(node)
  const isFakeCaretTarget = (node) => isInlineFakeCaretTarget(node) || (isTable$3(node) && isFakeCaretTableBrowser())

  const isContentEditableTrue$3 = isContentEditableTrue$5
  const isContentEditableFalse$7 = isContentEditableFalse$b
  const isMedia = isMedia$2
  const isBlockLike = matchStyleValues('display', 'block table table-cell table-caption list-item')
  const isCaretContainer = isCaretContainer$2
  const isCaretContainerBlock = isCaretContainerBlock$1
  const isElement$2 = isElement$6
  const isCaretCandidate$1 = isCaretCandidate$3
  const isForwards = (direction) => direction > 0
  const isBackwards = (direction) => direction < 0
  const skipCaretContainers = (walk, shallow) => {
    let node
    while ((node = walk(shallow))) {
      if (!isCaretContainerBlock(node)) {
        return node
      }
    }
    return null
  }
  const findNode = (node, direction, predicateFn, rootNode, shallow) => {
    const walker = new DomTreeWalker(node, rootNode)
    const isCefOrCaretContainer = isContentEditableFalse$7(node) || isCaretContainerBlock(node)
    if (isBackwards(direction)) {
      if (isCefOrCaretContainer) {
        node = skipCaretContainers(walker.prev.bind(walker), true)
        if (predicateFn(node)) {
          return node
        }
      }
      while ((node = skipCaretContainers(walker.prev.bind(walker), shallow))) {
        if (predicateFn(node)) {
          return node
        }
      }
    }
    if (isForwards(direction)) {
      if (isCefOrCaretContainer) {
        node = skipCaretContainers(walker.next.bind(walker), true)
        if (predicateFn(node)) {
          return node
        }
      }
      while ((node = skipCaretContainers(walker.next.bind(walker), shallow))) {
        if (predicateFn(node)) {
          return node
        }
      }
    }
    return null
  }
  const getEditingHost = (node, rootNode) => {
    const isCETrue = (node) => isContentEditableTrue$3(node.dom)
    const isRoot = (node) => node.dom === rootNode
    return ancestor$3(SugarElement.fromDom(node), isCETrue, isRoot)
      .map((elm) => elm.dom)
      .getOr(rootNode)
  }
  const getParentBlock$3 = (node, rootNode) => {
    while (node && node !== rootNode) {
      if (isBlockLike(node)) {
        return node
      }
      node = node.parentNode
    }
    return null
  }
  const isInSameBlock = (caretPosition1, caretPosition2, rootNode) =>
    getParentBlock$3(caretPosition1.container(), rootNode) === getParentBlock$3(caretPosition2.container(), rootNode)
  const getChildNodeAtRelativeOffset = (relativeOffset, caretPosition) => {
    if (!caretPosition) {
      return null
    }
    const container = caretPosition.container()
    const offset = caretPosition.offset()
    if (!isElement$2(container)) {
      return null
    }
    return container.childNodes[offset + relativeOffset]
  }
  const beforeAfter = (before, node) => {
    const range = node.ownerDocument.createRange()
    if (before) {
      range.setStartBefore(node)
      range.setEndBefore(node)
    } else {
      range.setStartAfter(node)
      range.setEndAfter(node)
    }
    return range
  }
  const isNodesInSameBlock = (root, node1, node2) => getParentBlock$3(node1, root) === getParentBlock$3(node2, root)
  const lean = (left, root, node) => {
    const siblingName = left ? 'previousSibling' : 'nextSibling'
    while (node && node !== root) {
      let sibling = node[siblingName]
      if (isCaretContainer(sibling)) {
        sibling = sibling[siblingName]
      }
      if (isContentEditableFalse$7(sibling) || isMedia(sibling)) {
        if (isNodesInSameBlock(root, sibling, node)) {
          return sibling
        }
        break
      }
      if (isCaretCandidate$1(sibling)) {
        break
      }
      node = node.parentNode
    }
    return null
  }
  const before$2 = curry(beforeAfter, true)
  const after$2 = curry(beforeAfter, false)
  const normalizeRange = (direction, root, range) => {
    let node
    const leanLeft = curry(lean, true, root)
    const leanRight = curry(lean, false, root)
    let container = range.startContainer
    const offset = range.startOffset
    if (isCaretContainerBlock$1(container)) {
      if (!isElement$2(container)) {
        container = container.parentNode
      }
      const location = container.getAttribute('data-mce-caret')
      if (location === 'before') {
        node = container.nextSibling
        if (isFakeCaretTarget(node)) {
          return before$2(node)
        }
      }
      if (location === 'after') {
        node = container.previousSibling
        if (isFakeCaretTarget(node)) {
          return after$2(node)
        }
      }
    }
    if (!range.collapsed) {
      return range
    }
    if (isText$9(container)) {
      if (isCaretContainer(container)) {
        if (direction === 1) {
          node = leanRight(container)
          if (node) {
            return before$2(node)
          }
          node = leanLeft(container)
          if (node) {
            return after$2(node)
          }
        }
        if (direction === -1) {
          node = leanLeft(container)
          if (node) {
            return after$2(node)
          }
          node = leanRight(container)
          if (node) {
            return before$2(node)
          }
        }
        return range
      }
      if (endsWithCaretContainer$1(container) && offset >= container.data.length - 1) {
        if (direction === 1) {
          node = leanRight(container)
          if (node) {
            return before$2(node)
          }
        }
        return range
      }
      if (startsWithCaretContainer$1(container) && offset <= 1) {
        if (direction === -1) {
          node = leanLeft(container)
          if (node) {
            return after$2(node)
          }
        }
        return range
      }
      if (offset === container.data.length) {
        node = leanRight(container)
        if (node) {
          return before$2(node)
        }
        return range
      }
      if (offset === 0) {
        node = leanLeft(container)
        if (node) {
          return after$2(node)
        }
        return range
      }
    }
    return range
  }
  const getRelativeCefElm = (forward, caretPosition) =>
    Optional.from(getChildNodeAtRelativeOffset(forward ? 0 : -1, caretPosition)).filter(isContentEditableFalse$7)
  const getNormalizedRangeEndPoint = (direction, root, range) => {
    const normalizedRange = normalizeRange(direction, root, range)
    if (direction === -1) {
      return CaretPosition.fromRangeStart(normalizedRange)
    }
    return CaretPosition.fromRangeEnd(normalizedRange)
  }
  const getElementFromPosition = (pos) => Optional.from(pos.getNode()).map(SugarElement.fromDom)
  const getElementFromPrevPosition = (pos) => Optional.from(pos.getNode(true)).map(SugarElement.fromDom)
  const getVisualCaretPosition = (walkFn, caretPosition) => {
    while ((caretPosition = walkFn(caretPosition))) {
      if (caretPosition.isVisible()) {
        return caretPosition
      }
    }
    return caretPosition
  }
  const isMoveInsideSameBlock = (from, to) => {
    const inSameBlock = isInSameBlock(from, to)
    if (!inSameBlock && isBr$6(from.getNode())) {
      return true
    }
    return inSameBlock
  }

  var HDirection
  ;(function (HDirection) {
    HDirection[(HDirection['Backwards'] = -1)] = 'Backwards'
    HDirection[(HDirection['Forwards'] = 1)] = 'Forwards'
  })(HDirection || (HDirection = {}))
  const isContentEditableFalse$6 = isContentEditableFalse$b
  const isText$3 = isText$9
  const isElement$1 = isElement$6
  const isBr$2 = isBr$6
  const isCaretCandidate = isCaretCandidate$3
  const isAtomic = isAtomic$1
  const isEditableCaretCandidate = isEditableCaretCandidate$1
  const getParents$3 = (node, root) => {
    const parents = []
    while (node && node !== root) {
      parents.push(node)
      node = node.parentNode
    }
    return parents
  }
  const nodeAtIndex = (container, offset) => {
    if (container.hasChildNodes() && offset < container.childNodes.length) {
      return container.childNodes[offset]
    }
    return null
  }
  const getCaretCandidatePosition = (direction, node) => {
    if (isForwards(direction)) {
      if (isCaretCandidate(node.previousSibling) && !isText$3(node.previousSibling)) {
        return CaretPosition.before(node)
      }
      if (isText$3(node)) {
        return CaretPosition(node, 0)
      }
    }
    if (isBackwards(direction)) {
      if (isCaretCandidate(node.nextSibling) && !isText$3(node.nextSibling)) {
        return CaretPosition.after(node)
      }
      if (isText$3(node)) {
        return CaretPosition(node, node.data.length)
      }
    }
    if (isBackwards(direction)) {
      if (isBr$2(node)) {
        return CaretPosition.before(node)
      }
      return CaretPosition.after(node)
    }
    return CaretPosition.before(node)
  }
  const moveForwardFromBr = (root, nextNode) => {
    const nextSibling = nextNode.nextSibling
    if (nextSibling && isCaretCandidate(nextSibling)) {
      if (isText$3(nextSibling)) {
        return CaretPosition(nextSibling, 0)
      } else {
        return CaretPosition.before(nextSibling)
      }
    } else {
      return findCaretPosition$1(HDirection.Forwards, CaretPosition.after(nextNode), root)
    }
  }
  const findCaretPosition$1 = (direction, startPos, root) => {
    let node
    let nextNode
    let innerNode
    let caretPosition
    if (!isElement$1(root) || !startPos) {
      return null
    }
    if (startPos.isEqual(CaretPosition.after(root)) && root.lastChild) {
      caretPosition = CaretPosition.after(root.lastChild)
      if (isBackwards(direction) && isCaretCandidate(root.lastChild) && isElement$1(root.lastChild)) {
        return isBr$2(root.lastChild) ? CaretPosition.before(root.lastChild) : caretPosition
      }
    } else {
      caretPosition = startPos
    }
    const container = caretPosition.container()
    let offset = caretPosition.offset()
    if (isText$3(container)) {
      if (isBackwards(direction) && offset > 0) {
        return CaretPosition(container, --offset)
      }
      if (isForwards(direction) && offset < container.length) {
        return CaretPosition(container, ++offset)
      }
      node = container
    } else {
      if (isBackwards(direction) && offset > 0) {
        nextNode = nodeAtIndex(container, offset - 1)
        if (isCaretCandidate(nextNode)) {
          if (!isAtomic(nextNode)) {
            innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode)
            if (innerNode) {
              if (isText$3(innerNode)) {
                return CaretPosition(innerNode, innerNode.data.length)
              }
              return CaretPosition.after(innerNode)
            }
          }
          if (isText$3(nextNode)) {
            return CaretPosition(nextNode, nextNode.data.length)
          }
          return CaretPosition.before(nextNode)
        }
      }
      if (isForwards(direction) && offset < container.childNodes.length) {
        nextNode = nodeAtIndex(container, offset)
        if (isCaretCandidate(nextNode)) {
          if (isBr$2(nextNode)) {
            return moveForwardFromBr(root, nextNode)
          }
          if (!isAtomic(nextNode)) {
            innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode)
            if (innerNode) {
              if (isText$3(innerNode)) {
                return CaretPosition(innerNode, 0)
              }
              return CaretPosition.before(innerNode)
            }
          }
          if (isText$3(nextNode)) {
            return CaretPosition(nextNode, 0)
          }
          return CaretPosition.after(nextNode)
        }
      }
      node = nextNode ? nextNode : caretPosition.getNode()
    }
    if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
      node = findNode(node, direction, always, root, true)
      if (isEditableCaretCandidate(node, root)) {
        return getCaretCandidatePosition(direction, node)
      }
    }
    nextNode = findNode(node, direction, isEditableCaretCandidate, root)
    const rootContentEditableFalseElm = last$2(filter$6(getParents$3(container, root), isContentEditableFalse$6))
    if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
      if (isForwards(direction)) {
        caretPosition = CaretPosition.after(rootContentEditableFalseElm)
      } else {
        caretPosition = CaretPosition.before(rootContentEditableFalseElm)
      }
      return caretPosition
    }
    if (nextNode) {
      return getCaretCandidatePosition(direction, nextNode)
    }
    return null
  }
  const CaretWalker = (root) => ({
    next: (caretPosition) => {
      return findCaretPosition$1(HDirection.Forwards, caretPosition, root)
    },
    prev: (caretPosition) => {
      return findCaretPosition$1(HDirection.Backwards, caretPosition, root)
    },
  })

  const walkToPositionIn = (forward, root, start) => {
    const position = forward ? CaretPosition.before(start) : CaretPosition.after(start)
    return fromPosition(forward, root, position)
  }
  const afterElement = (node) => (isBr$6(node) ? CaretPosition.before(node) : CaretPosition.after(node))
  const isBeforeOrStart = (position) => {
    if (CaretPosition.isTextPosition(position)) {
      return position.offset() === 0
    } else {
      return isCaretCandidate$3(position.getNode())
    }
  }
  const isAfterOrEnd = (position) => {
    if (CaretPosition.isTextPosition(position)) {
      const container = position.container()
      return position.offset() === container.data.length
    } else {
      return isCaretCandidate$3(position.getNode(true))
    }
  }
  const isBeforeAfterSameElement = (from, to) =>
    !CaretPosition.isTextPosition(from) && !CaretPosition.isTextPosition(to) && from.getNode() === to.getNode(true)
  const isAtBr = (position) => !CaretPosition.isTextPosition(position) && isBr$6(position.getNode())
  const shouldSkipPosition = (forward, from, to) => {
    if (forward) {
      return !isBeforeAfterSameElement(from, to) && !isAtBr(from) && isAfterOrEnd(from) && isBeforeOrStart(to)
    } else {
      return !isBeforeAfterSameElement(to, from) && isBeforeOrStart(from) && isAfterOrEnd(to)
    }
  }
  const fromPosition = (forward, root, pos) => {
    const walker = CaretWalker(root)
    return Optional.from(forward ? walker.next(pos) : walker.prev(pos))
  }
  const navigate = (forward, root, from) =>
    fromPosition(forward, root, from).bind((to) => {
      if (isInSameBlock(from, to, root) && shouldSkipPosition(forward, from, to)) {
        return fromPosition(forward, root, to)
      } else {
        return Optional.some(to)
      }
    })
  const navigateIgnore = (forward, root, from, ignoreFilter) =>
    navigate(forward, root, from).bind((pos) =>
      ignoreFilter(pos) ? navigateIgnore(forward, root, pos, ignoreFilter) : Optional.some(pos)
    )
  const positionIn = (forward, element) => {
    const startNode = forward ? element.firstChild : element.lastChild
    if (isText$9(startNode)) {
      return Optional.some(CaretPosition(startNode, forward ? 0 : startNode.data.length))
    } else if (startNode) {
      if (isCaretCandidate$3(startNode)) {
        return Optional.some(forward ? CaretPosition.before(startNode) : afterElement(startNode))
      } else {
        return walkToPositionIn(forward, element, startNode)
      }
    } else {
      return Optional.none()
    }
  }
  const nextPosition = curry(fromPosition, true)
  const prevPosition = curry(fromPosition, false)
  const firstPositionIn = curry(positionIn, true)
  const lastPositionIn = curry(positionIn, false)

  const CARET_ID$1 = '_mce_caret'
  const isCaretNode = (node) => isElement$6(node) && node.id === CARET_ID$1
  const getParentCaretContainer = (body, node) => {
    while (node && node !== body) {
      if (node.id === CARET_ID$1) {
        return node
      }
      node = node.parentNode
    }
    return null
  }

  const isStringPathBookmark = (bookmark) => isString(bookmark.start)
  const isRangeBookmark = (bookmark) => has$2(bookmark, 'rng')
  const isIdBookmark = (bookmark) => has$2(bookmark, 'id')
  const isIndexBookmark = (bookmark) => has$2(bookmark, 'name')
  const isPathBookmark = (bookmark) => Tools.isArray(bookmark.start)

  const isForwardBookmark = (bookmark) =>
    !isIndexBookmark(bookmark) && isBoolean(bookmark.forward) ? bookmark.forward : true
  const addBogus = (dom, node) => {
    if (isElement$6(node) && dom.isBlock(node) && !node.innerHTML) {
      node.innerHTML = '<br data-mce-bogus="1" />'
    }
    return node
  }
  const resolveCaretPositionBookmark = (dom, bookmark) => {
    const range = dom.createRng()
    const startPos = resolve$1(dom.getRoot(), bookmark.start)
    range.setStart(startPos.container(), startPos.offset())
    const endPos = resolve$1(dom.getRoot(), bookmark.end)
    range.setEnd(endPos.container(), endPos.offset())
    return {
      range,
      forward: isForwardBookmark(bookmark),
    }
  }
  const insertZwsp = (node, rng) => {
    const textNode = node.ownerDocument.createTextNode(ZWSP$1)
    node.appendChild(textNode)
    rng.setStart(textNode, 0)
    rng.setEnd(textNode, 0)
  }
  const isEmpty$1 = (node) => node.hasChildNodes() === false
  const tryFindRangePosition = (node, rng) =>
    lastPositionIn(node).fold(never, (pos) => {
      rng.setStart(pos.container(), pos.offset())
      rng.setEnd(pos.container(), pos.offset())
      return true
    })
  const padEmptyCaretContainer = (root, node, rng) => {
    if (isEmpty$1(node) && getParentCaretContainer(root, node)) {
      insertZwsp(node, rng)
      return true
    } else {
      return false
    }
  }
  const setEndPoint = (dom, start, bookmark, rng) => {
    const point = bookmark[start ? 'start' : 'end']
    let i, node, offset, children
    const root = dom.getRoot()
    if (point) {
      offset = point[0]
      for (node = root, i = point.length - 1; i >= 1; i--) {
        children = node.childNodes
        if (padEmptyCaretContainer(root, node, rng)) {
          return true
        }
        if (point[i] > children.length - 1) {
          if (padEmptyCaretContainer(root, node, rng)) {
            return true
          }
          return tryFindRangePosition(node, rng)
        }
        node = children[point[i]]
      }
      if (node.nodeType === 3) {
        offset = Math.min(point[0], node.nodeValue.length)
      }
      if (node.nodeType === 1) {
        offset = Math.min(point[0], node.childNodes.length)
      }
      if (start) {
        rng.setStart(node, offset)
      } else {
        rng.setEnd(node, offset)
      }
    }
    return true
  }
  const isValidTextNode = (node) => isText$9(node) && node.data.length > 0
  const restoreEndPoint = (dom, suffix, bookmark) => {
    let marker = dom.get(bookmark.id + '_' + suffix),
      node,
      idx,
      next,
      prev
    const keep = bookmark.keep
    let container, offset
    if (marker) {
      node = marker.parentNode
      if (suffix === 'start') {
        if (!keep) {
          idx = dom.nodeIndex(marker)
        } else {
          if (marker.hasChildNodes()) {
            node = marker.firstChild
            idx = 1
          } else if (isValidTextNode(marker.nextSibling)) {
            node = marker.nextSibling
            idx = 0
          } else if (isValidTextNode(marker.previousSibling)) {
            node = marker.previousSibling
            idx = marker.previousSibling.data.length
          } else {
            node = marker.parentNode
            idx = dom.nodeIndex(marker) + 1
          }
        }
        container = node
        offset = idx
      } else {
        if (!keep) {
          idx = dom.nodeIndex(marker)
        } else {
          if (marker.hasChildNodes()) {
            node = marker.firstChild
            idx = 1
          } else if (isValidTextNode(marker.previousSibling)) {
            node = marker.previousSibling
            idx = marker.previousSibling.data.length
          } else {
            node = marker.parentNode
            idx = dom.nodeIndex(marker)
          }
        }
        container = node
        offset = idx
      }
      if (!keep) {
        prev = marker.previousSibling
        next = marker.nextSibling
        Tools.each(Tools.grep(marker.childNodes), (node) => {
          if (isText$9(node)) {
            node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '')
          }
        })
        while ((marker = dom.get(bookmark.id + '_' + suffix))) {
          dom.remove(marker, true)
        }
        if (prev && next && prev.nodeType === next.nodeType && isText$9(prev) && !Env.browser.isOpera()) {
          idx = prev.nodeValue.length
          prev.appendData(next.nodeValue)
          dom.remove(next)
          container = prev
          offset = idx
        }
      }
      return Optional.some(CaretPosition(container, offset))
    } else {
      return Optional.none()
    }
  }
  const resolvePaths = (dom, bookmark) => {
    const range = dom.createRng()
    if (setEndPoint(dom, true, bookmark, range) && setEndPoint(dom, false, bookmark, range)) {
      return Optional.some({
        range,
        forward: isForwardBookmark(bookmark),
      })
    } else {
      return Optional.none()
    }
  }
  const resolveId = (dom, bookmark) => {
    const startPos = restoreEndPoint(dom, 'start', bookmark)
    const endPos = restoreEndPoint(dom, 'end', bookmark)
    return lift2(startPos, endPos.or(startPos), (spos, epos) => {
      const range = dom.createRng()
      range.setStart(addBogus(dom, spos.container()), spos.offset())
      range.setEnd(addBogus(dom, epos.container()), epos.offset())
      return {
        range,
        forward: isForwardBookmark(bookmark),
      }
    })
  }
  const resolveIndex = (dom, bookmark) =>
    Optional.from(dom.select(bookmark.name)[bookmark.index]).map((elm) => {
      const range = dom.createRng()
      range.selectNode(elm)
      return {
        range,
        forward: true,
      }
    })
  const resolve = (selection, bookmark) => {
    const dom = selection.dom
    if (bookmark) {
      if (isPathBookmark(bookmark)) {
        return resolvePaths(dom, bookmark)
      } else if (isStringPathBookmark(bookmark)) {
        return Optional.some(resolveCaretPositionBookmark(dom, bookmark))
      } else if (isIdBookmark(bookmark)) {
        return resolveId(dom, bookmark)
      } else if (isIndexBookmark(bookmark)) {
        return resolveIndex(dom, bookmark)
      } else if (isRangeBookmark(bookmark)) {
        return Optional.some({
          range: bookmark.rng,
          forward: isForwardBookmark(bookmark),
        })
      }
    }
    return Optional.none()
  }

  const getBookmark$1 = (selection, type, normalized) => {
    return getBookmark$2(selection, type, normalized)
  }
  const moveToBookmark = (selection, bookmark) => {
    resolve(selection, bookmark).each(({ range, forward }) => {
      selection.setRng(range, forward)
    })
  }
  const isBookmarkNode$1 = (node) => {
    return isElement$6(node) && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark'
  }

  const is = (expected) => (actual) => expected === actual
  const isNbsp = is(nbsp)
  const isWhiteSpace = (chr) => chr !== '' && ' \f\n\r\t\x0B'.indexOf(chr) !== -1
  const isContent = (chr) => !isWhiteSpace(chr) && !isNbsp(chr) && !isZwsp$1(chr)

  const hexColour = (value) => ({ value })
  const toHex = (component) => {
    const hex = component.toString(16)
    return (hex.length === 1 ? '0' + hex : hex).toUpperCase()
  }
  const fromRgba = (rgbaColour) => {
    const value = toHex(rgbaColour.red) + toHex(rgbaColour.green) + toHex(rgbaColour.blue)
    return hexColour(value)
  }

  const rgbRegex = /^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/i
  const rgbaRegex = /^\s*rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?(?:\.\d+)?)\s*\)\s*$/i
  const rgbaColour = (red, green, blue, alpha) => ({
    red,
    green,
    blue,
    alpha,
  })
  const fromStringValues = (red, green, blue, alpha) => {
    const r = parseInt(red, 10)
    const g = parseInt(green, 10)
    const b = parseInt(blue, 10)
    const a = parseFloat(alpha)
    return rgbaColour(r, g, b, a)
  }
  const fromString = (rgbaString) => {
    if (rgbaString === 'transparent') {
      return Optional.some(rgbaColour(0, 0, 0, 0))
    }
    const rgbMatch = rgbRegex.exec(rgbaString)
    if (rgbMatch !== null) {
      return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], rgbMatch[3], '1'))
    }
    const rgbaMatch = rgbaRegex.exec(rgbaString)
    if (rgbaMatch !== null) {
      return Optional.some(fromStringValues(rgbaMatch[1], rgbaMatch[2], rgbaMatch[3], rgbaMatch[4]))
    }
    return Optional.none()
  }

  const rgbaToHexString = (color) =>
    fromString(color)
      .map(fromRgba)
      .map((h) => '#' + h.value)
      .getOr(color)

  const isNode = (node) => !!node.nodeType
  const isInlineBlock = (node) => {
    return node && /^(IMG)$/.test(node.nodeName)
  }
  const moveStart = (dom, selection, rng) => {
    const offset = rng.startOffset
    let container = rng.startContainer
    if (container === rng.endContainer) {
      if (isInlineBlock(container.childNodes[offset])) {
        return
      }
    }
    if (isElement$6(container)) {
      const nodes = container.childNodes
      let walker
      if (offset < nodes.length) {
        container = nodes[offset]
        walker = new DomTreeWalker(container, dom.getParent(container, dom.isBlock))
      } else {
        container = nodes[nodes.length - 1]
        walker = new DomTreeWalker(container, dom.getParent(container, dom.isBlock))
        walker.next(true)
      }
      for (let node = walker.current(); node; node = walker.next()) {
        if (isText$9(node) && !isWhiteSpaceNode$1(node)) {
          rng.setStart(node, 0)
          selection.setRng(rng)
          return
        }
      }
    }
  }
  const getNonWhiteSpaceSibling = (node, next, inc) => {
    if (node) {
      const nextName = next ? 'nextSibling' : 'previousSibling'
      for (node = inc ? node : node[nextName]; node; node = node[nextName]) {
        if (isElement$6(node) || !isWhiteSpaceNode$1(node)) {
          return node
        }
      }
    }
  }
  const isTextBlock$1 = (editor, name) => {
    if (isNode(name)) {
      name = name.nodeName
    }
    return !!editor.schema.getTextBlockElements()[name.toLowerCase()]
  }
  const isValid = (ed, parent, child) => {
    return ed.schema.isValidChild(parent, child)
  }
  const isWhiteSpaceNode$1 = (node, allowSpaces = false) => {
    if (isNonNullable(node) && isText$9(node)) {
      const data = allowSpaces ? node.data.replace(/ /g, '\xA0') : node.data
      return isWhitespaceText(data)
    } else {
      return false
    }
  }
  const isEmptyTextNode$1 = (node) => {
    return isNonNullable(node) && isText$9(node) && node.length === 0
  }
  const replaceVars = (value, vars) => {
    if (isFunction(value)) {
      value = value(vars)
    } else if (isNonNullable(vars)) {
      value = value.replace(/%(\w+)/g, (str, name) => {
        return vars[name] || str
      })
    }
    return value
  }
  const isEq$5 = (str1, str2) => {
    str1 = str1 || ''
    str2 = str2 || ''
    str1 = '' + (str1.nodeName || str1)
    str2 = '' + (str2.nodeName || str2)
    return str1.toLowerCase() === str2.toLowerCase()
  }
  const normalizeStyleValue = (value, name) => {
    if (name === 'color' || name === 'backgroundColor') {
      value = rgbaToHexString(value)
    }
    if (name === 'fontWeight' && value === 700) {
      value = 'bold'
    }
    if (name === 'fontFamily') {
      value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',')
    }
    return '' + value
  }
  const getStyle = (dom, node, name) => {
    return normalizeStyleValue(dom.getStyle(node, name), name)
  }
  const getTextDecoration = (dom, node) => {
    let decoration
    dom.getParent(node, (n) => {
      decoration = dom.getStyle(n, 'text-decoration')
      return decoration && decoration !== 'none'
    })
    return decoration
  }
  const getParents$2 = (dom, node, selector) => {
    return dom.getParents(node, selector, dom.getRoot())
  }
  const isVariableFormatName = (editor, formatName) => {
    const hasVariableValues = (format) => {
      const isVariableValue = (val) => val.length > 1 && val.charAt(0) === '%'
      return exists(['styles', 'attributes'], (key) =>
        get$a(format, key).exists((field) => {
          const fieldValues = isArray$1(field) ? field : values(field)
          return exists(fieldValues, isVariableValue)
        })
      )
    }
    return exists(editor.formatter.get(formatName), hasVariableValues)
  }
  const areSimilarFormats = (editor, formatName, otherFormatName) => {
    const validKeys = ['inline', 'block', 'selector', 'attributes', 'styles', 'classes']
    const filterObj = (format) => filter$5(format, (_, key) => exists(validKeys, (validKey) => validKey === key))
    return exists(editor.formatter.get(formatName), (fmt1) => {
      const filteredFmt1 = filterObj(fmt1)
      return exists(editor.formatter.get(otherFormatName), (fmt2) => {
        const filteredFmt2 = filterObj(fmt2)
        return equal$1(filteredFmt1, filteredFmt2)
      })
    })
  }
  const isBlockFormat = (format) => hasNonNullableKey(format, 'block')
  const isSelectorFormat = (format) => hasNonNullableKey(format, 'selector')
  const isInlineFormat = (format) => hasNonNullableKey(format, 'inline')
  const isMixedFormat = (format) =>
    isSelectorFormat(format) && isInlineFormat(format) && is$2(get$a(format, 'mixed'), true)
  const shouldExpandToSelector = (format) =>
    isSelectorFormat(format) && format.expand !== false && !isInlineFormat(format)

  const isBookmarkNode = isBookmarkNode$1
  const getParents$1 = getParents$2
  const isWhiteSpaceNode = isWhiteSpaceNode$1
  const isTextBlock = isTextBlock$1
  const isBogusBr = (node) => {
    return isBr$6(node) && node.getAttribute('data-mce-bogus') && !node.nextSibling
  }
  const findParentContentEditable = (dom, node) => {
    let parent = node
    while (parent) {
      if (isElement$6(parent) && dom.getContentEditable(parent)) {
        return dom.getContentEditable(parent) === 'false' ? parent : node
      }
      parent = parent.parentNode
    }
    return node
  }
  const walkText = (start, node, offset, predicate) => {
    const str = node.data
    for (let i = offset; start ? i >= 0 : i < str.length; start ? i-- : i++) {
      if (predicate(str.charAt(i))) {
        return start ? i + 1 : i
      }
    }
    return -1
  }
  const findSpace = (start, node, offset) => walkText(start, node, offset, (c) => isNbsp(c) || isWhiteSpace(c))
  const findContent = (start, node, offset) => walkText(start, node, offset, isContent)
  const findWordEndPoint = (dom, body, container, offset, start, includeTrailingSpaces) => {
    let lastTextNode
    const rootNode = dom.getParent(container, dom.isBlock) || body
    const walk = (container, offset, pred) => {
      const textSeeker = TextSeeker(dom)
      const walker = start ? textSeeker.backwards : textSeeker.forwards
      return Optional.from(
        walker(
          container,
          offset,
          (text, textOffset) => {
            if (isBookmarkNode(text.parentNode)) {
              return -1
            } else {
              lastTextNode = text
              return pred(start, text, textOffset)
            }
          },
          rootNode
        )
      )
    }
    const spaceResult = walk(container, offset, findSpace)
    return spaceResult
      .bind((result) =>
        includeTrailingSpaces
          ? walk(result.container, result.offset + (start ? -1 : 0), findContent)
          : Optional.some(result)
      )
      .orThunk(() =>
        lastTextNode
          ? Optional.some({
              container: lastTextNode,
              offset: start ? 0 : lastTextNode.length,
            })
          : Optional.none()
      )
  }
  const findSelectorEndPoint = (dom, formatList, rng, container, siblingName) => {
    if (isText$9(container) && isEmpty$3(container.data) && container[siblingName]) {
      container = container[siblingName]
    }
    const parents = getParents$1(dom, container)
    for (let i = 0; i < parents.length; i++) {
      for (let y = 0; y < formatList.length; y++) {
        const curFormat = formatList[y]
        if (isNonNullable(curFormat.collapsed) && curFormat.collapsed !== rng.collapsed) {
          continue
        }
        if (isSelectorFormat(curFormat) && dom.is(parents[i], curFormat.selector)) {
          return parents[i]
        }
      }
    }
    return container
  }
  const findBlockEndPoint = (editor, formatList, container, siblingName) => {
    let node = container
    const dom = editor.dom
    const root = dom.getRoot()
    const format = formatList[0]
    if (isBlockFormat(format)) {
      node = format.wrapper ? null : dom.getParent(container, format.block, root)
    }
    if (!node) {
      const scopeRoot = dom.getParent(container, 'LI,TD,TH')
      node = dom.getParent(
        isText$9(container) ? container.parentNode : container,
        (node) => node !== root && isTextBlock(editor, node),
        scopeRoot
      )
    }
    if (node && isBlockFormat(format) && format.wrapper) {
      node = getParents$1(dom, node, 'ul,ol').reverse()[0] || node
    }
    if (!node) {
      node = container
      while (node[siblingName] && !dom.isBlock(node[siblingName])) {
        node = node[siblingName]
        if (isEq$5(node, 'br')) {
          break
        }
      }
    }
    return node || container
  }
  const isAtBlockBoundary$1 = (dom, root, container, siblingName) => {
    const parent = container.parentNode
    if (isNonNullable(container[siblingName])) {
      return false
    } else if (parent === root || isNullable(parent) || dom.isBlock(parent)) {
      return true
    } else {
      return isAtBlockBoundary$1(dom, root, parent, siblingName)
    }
  }
  const findParentContainer = (dom, formatList, container, offset, start) => {
    let parent = container
    const siblingName = start ? 'previousSibling' : 'nextSibling'
    const root = dom.getRoot()
    if (isText$9(container) && !isWhiteSpaceNode(container)) {
      if (start ? offset > 0 : offset < container.data.length) {
        return container
      }
    }
    while (true) {
      if (!formatList[0].block_expand && dom.isBlock(parent)) {
        return parent
      }
      for (let sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
        const allowSpaces = isText$9(sibling) && !isAtBlockBoundary$1(dom, root, sibling, siblingName)
        if (!isBookmarkNode(sibling) && !isBogusBr(sibling) && !isWhiteSpaceNode(sibling, allowSpaces)) {
          return parent
        }
      }
      if (parent === root || parent.parentNode === root) {
        container = parent
        break
      }
      parent = parent.parentNode
    }
    return container
  }
  const isSelfOrParentBookmark = (container) => isBookmarkNode(container.parentNode) || isBookmarkNode(container)
  const expandRng = (editor, rng, formatList, includeTrailingSpace = false) => {
    let { startContainer, startOffset, endContainer, endOffset } = rng
    const dom = editor.dom
    const format = formatList[0]
    if (isElement$6(startContainer) && startContainer.hasChildNodes()) {
      startContainer = getNode$1(startContainer, startOffset)
      if (isText$9(startContainer)) {
        startOffset = 0
      }
    }
    if (isElement$6(endContainer) && endContainer.hasChildNodes()) {
      endContainer = getNode$1(endContainer, rng.collapsed ? endOffset : endOffset - 1)
      if (isText$9(endContainer)) {
        endOffset = endContainer.nodeValue.length
      }
    }
    startContainer = findParentContentEditable(dom, startContainer)
    endContainer = findParentContentEditable(dom, endContainer)
    if (isSelfOrParentBookmark(startContainer)) {
      startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode
      if (rng.collapsed) {
        startContainer = startContainer.previousSibling || startContainer
      } else {
        startContainer = startContainer.nextSibling || startContainer
      }
      if (isText$9(startContainer)) {
        startOffset = rng.collapsed ? startContainer.length : 0
      }
    }
    if (isSelfOrParentBookmark(endContainer)) {
      endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode
      if (rng.collapsed) {
        endContainer = endContainer.nextSibling || endContainer
      } else {
        endContainer = endContainer.previousSibling || endContainer
      }
      if (isText$9(endContainer)) {
        endOffset = rng.collapsed ? 0 : endContainer.length
      }
    }
    if (rng.collapsed) {
      const startPoint = findWordEndPoint(
        dom,
        editor.getBody(),
        startContainer,
        startOffset,
        true,
        includeTrailingSpace
      )
      startPoint.each(({ container, offset }) => {
        startContainer = container
        startOffset = offset
      })
      const endPoint = findWordEndPoint(dom, editor.getBody(), endContainer, endOffset, false, includeTrailingSpace)
      endPoint.each(({ container, offset }) => {
        endContainer = container
        endOffset = offset
      })
    }
    if (isInlineFormat(format) || format.block_expand) {
      if (!isInlineFormat(format) || !isText$9(startContainer) || startOffset === 0) {
        startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true)
      }
      if (!isInlineFormat(format) || !isText$9(endContainer) || endOffset === endContainer.nodeValue.length) {
        endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false)
      }
    }
    if (shouldExpandToSelector(format)) {
      startContainer = findSelectorEndPoint(dom, formatList, rng, startContainer, 'previousSibling')
      endContainer = findSelectorEndPoint(dom, formatList, rng, endContainer, 'nextSibling')
    }
    if (isBlockFormat(format) || isSelectorFormat(format)) {
      startContainer = findBlockEndPoint(editor, formatList, startContainer, 'previousSibling')
      endContainer = findBlockEndPoint(editor, formatList, endContainer, 'nextSibling')
      if (isBlockFormat(format)) {
        if (!dom.isBlock(startContainer)) {
          startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true)
        }
        if (!dom.isBlock(endContainer)) {
          endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false)
        }
      }
    }
    if (isElement$6(startContainer)) {
      startOffset = dom.nodeIndex(startContainer)
      startContainer = startContainer.parentNode
    }
    if (isElement$6(endContainer)) {
      endOffset = dom.nodeIndex(endContainer) + 1
      endContainer = endContainer.parentNode
    }
    return {
      startContainer,
      startOffset,
      endContainer,
      endOffset,
    }
  }

  const walk$3 = (dom, rng, callback) => {
    const startOffset = rng.startOffset
    const startContainer = getNode$1(rng.startContainer, startOffset)
    const endOffset = rng.endOffset
    const endContainer = getNode$1(rng.endContainer, endOffset - 1)
    const exclude = (nodes) => {
      const firstNode = nodes[0]
      if (isText$9(firstNode) && firstNode === startContainer && startOffset >= firstNode.data.length) {
        nodes.splice(0, 1)
      }
      const lastNode = nodes[nodes.length - 1]
      if (endOffset === 0 && nodes.length > 0 && lastNode === endContainer && isText$9(lastNode)) {
        nodes.splice(nodes.length - 1, 1)
      }
      return nodes
    }
    const collectSiblings = (node, name, endNode) => {
      const siblings = []
      for (; node && node !== endNode; node = node[name]) {
        siblings.push(node)
      }
      return siblings
    }
    const findEndPoint = (node, root) => dom.getParent(node, (node) => node.parentNode === root, root)
    const walkBoundary = (startNode, endNode, next) => {
      const siblingName = next ? 'nextSibling' : 'previousSibling'
      for (let node = startNode, parent = node.parentNode; node && node !== endNode; node = parent) {
        parent = node.parentNode
        const siblings = collectSiblings(node === startNode ? node : node[siblingName], siblingName)
        if (siblings.length) {
          if (!next) {
            siblings.reverse()
          }
          callback(exclude(siblings))
        }
      }
    }
    if (startContainer === endContainer) {
      return callback(exclude([startContainer]))
    }
    const ancestor = dom.findCommonAncestor(startContainer, endContainer)
    if (dom.isChildOf(startContainer, endContainer)) {
      return walkBoundary(startContainer, ancestor, true)
    }
    if (dom.isChildOf(endContainer, startContainer)) {
      return walkBoundary(endContainer, ancestor)
    }
    const startPoint = findEndPoint(startContainer, ancestor) || startContainer
    const endPoint = findEndPoint(endContainer, ancestor) || endContainer
    walkBoundary(startContainer, startPoint, true)
    const siblings = collectSiblings(
      startPoint === startContainer ? startPoint : startPoint.nextSibling,
      'nextSibling',
      endPoint === endContainer ? endPoint.nextSibling : endPoint
    )
    if (siblings.length) {
      callback(exclude(siblings))
    }
    walkBoundary(endContainer, endPoint)
  }

  const getRanges$1 = (selection) => {
    const ranges = []
    if (selection) {
      for (let i = 0; i < selection.rangeCount; i++) {
        ranges.push(selection.getRangeAt(i))
      }
    }
    return ranges
  }
  const getSelectedNodes = (ranges) => {
    return bind$3(ranges, (range) => {
      const node = getSelectedNode(range)
      return node ? [SugarElement.fromDom(node)] : []
    })
  }
  const hasMultipleRanges = (selection) => {
    return getRanges$1(selection).length > 1
  }

  const getCellsFromRanges = (ranges) => filter$6(getSelectedNodes(ranges), isTableCell$4)
  const getCellsFromElement = (elm) => descendants(elm, 'td[data-mce-selected],th[data-mce-selected]')
  const getCellsFromElementOrRanges = (ranges, element) => {
    const selectedCells = getCellsFromElement(element)
    return selectedCells.length > 0 ? selectedCells : getCellsFromRanges(ranges)
  }
  const getCellsFromEditor = (editor) =>
    getCellsFromElementOrRanges(getRanges$1(editor.selection.getSel()), SugarElement.fromDom(editor.getBody()))
  const getClosestTable = (cell, isRoot) => ancestor$2(cell, 'table', isRoot)

  const getStartNode = (rng) => {
    const sc = rng.startContainer,
      so = rng.startOffset
    if (isText$9(sc)) {
      return so === 0 ? Optional.some(SugarElement.fromDom(sc)) : Optional.none()
    } else {
      return Optional.from(sc.childNodes[so]).map(SugarElement.fromDom)
    }
  }
  const getEndNode = (rng) => {
    const ec = rng.endContainer,
      eo = rng.endOffset
    if (isText$9(ec)) {
      return eo === ec.data.length ? Optional.some(SugarElement.fromDom(ec)) : Optional.none()
    } else {
      return Optional.from(ec.childNodes[eo - 1]).map(SugarElement.fromDom)
    }
  }
  const getFirstChildren = (node) => {
    return firstChild(node).fold(constant([node]), (child) => {
      return [node].concat(getFirstChildren(child))
    })
  }
  const getLastChildren = (node) => {
    return lastChild(node).fold(constant([node]), (child) => {
      if (name(child) === 'br') {
        return prevSibling(child)
          .map((sibling) => {
            return [node].concat(getLastChildren(sibling))
          })
          .getOr([])
      } else {
        return [node].concat(getLastChildren(child))
      }
    })
  }
  const hasAllContentsSelected = (elm, rng) => {
    return lift2(getStartNode(rng), getEndNode(rng), (startNode, endNode) => {
      const start = find$2(getFirstChildren(elm), curry(eq, startNode))
      const end = find$2(getLastChildren(elm), curry(eq, endNode))
      return start.isSome() && end.isSome()
    }).getOr(false)
  }
  const moveEndPoint = (dom, rng, node, start) => {
    const root = node,
      walker = new DomTreeWalker(node, root)
    const moveCaretBeforeOnEnterElementsMap = filter$5(
      dom.schema.getMoveCaretBeforeOnEnterElements(),
      (_, name) => !contains$2(['td', 'th', 'table'], name.toLowerCase())
    )
    do {
      if (isText$9(node) && Tools.trim(node.nodeValue).length !== 0) {
        if (start) {
          rng.setStart(node, 0)
        } else {
          rng.setEnd(node, node.nodeValue.length)
        }
        return
      }
      if (moveCaretBeforeOnEnterElementsMap[node.nodeName]) {
        if (start) {
          rng.setStartBefore(node)
        } else {
          if (node.nodeName === 'BR') {
            rng.setEndBefore(node)
          } else {
            rng.setEndAfter(node)
          }
        }
        return
      }
    } while ((node = start ? walker.next() : walker.prev()))
    if (root.nodeName === 'BODY') {
      if (start) {
        rng.setStart(root, 0)
      } else {
        rng.setEnd(root, root.childNodes.length)
      }
    }
  }
  const hasAnyRanges = (editor) => {
    const sel = editor.selection.getSel()
    return sel && sel.rangeCount > 0
  }
  const runOnRanges = (editor, executor) => {
    const fakeSelectionNodes = getCellsFromEditor(editor)
    if (fakeSelectionNodes.length > 0) {
      each$f(fakeSelectionNodes, (elem) => {
        const node = elem.dom
        const fakeNodeRng = editor.dom.createRng()
        fakeNodeRng.setStartBefore(node)
        fakeNodeRng.setEndAfter(node)
        executor(fakeNodeRng, true)
      })
    } else {
      executor(editor.selection.getRng(), false)
    }
  }
  const preserve = (selection, fillBookmark, executor) => {
    const bookmark = getPersistentBookmark(selection, fillBookmark)
    executor(bookmark)
    selection.moveToBookmark(bookmark)
  }

  const validBlocks = [
    'pre[class*=language-][contenteditable="false"]',
    'figure.image',
    'div[data-ephox-embed-iri]',
    'div.tiny-pageembed',
    'div.mce-toc',
    'div[data-mce-toc]',
  ]
  const isZeroWidth = (elem) => isText$a(elem) && get$3(elem) === ZWSP$1
  const context = (editor, elem, wrapName, nodeName) =>
    parent(elem).fold(
      () => 'skipping',
      (parent) => {
        if (nodeName === 'br' || isZeroWidth(elem)) {
          return 'valid'
        } else if (isAnnotation(elem)) {
          return 'existing'
        } else if (isCaretNode(elem.dom)) {
          return 'caret'
        } else if (exists(validBlocks, (selector) => is$1(elem, selector))) {
          return 'valid-block'
        } else if (!isValid(editor, wrapName, nodeName) || !isValid(editor, name(parent), wrapName)) {
          return 'invalid-child'
        } else {
          return 'valid'
        }
      }
    )

  const applyWordGrab = (editor, rng) => {
    const r = expandRng(editor, rng, [{ inline: 'span' }])
    rng.setStart(r.startContainer, r.startOffset)
    rng.setEnd(r.endContainer, r.endOffset)
    editor.selection.setRng(rng)
  }
  const applyAnnotation = (elem, masterUId, data, annotationName, decorate, directAnnotation) => {
    const { uid = masterUId, ...otherData } = data
    add$2(elem, annotation())
    set$2(elem, `${dataAnnotationId()}`, uid)
    set$2(elem, `${dataAnnotation()}`, annotationName)
    const { attributes = {}, classes = [] } = decorate(uid, otherData)
    setAll$1(elem, attributes)
    add(elem, classes)
    if (directAnnotation) {
      if (classes.length > 0) {
        set$2(elem, `${dataAnnotationClasses()}`, classes.join(','))
      }
      const attributeNames = keys(attributes)
      if (attributeNames.length > 0) {
        set$2(elem, `${dataAnnotationAttributes()}`, attributeNames.join(','))
      }
    }
  }
  const removeDirectAnnotation = (elem) => {
    remove$8(elem, annotation())
    remove$b(elem, `${dataAnnotationId()}`)
    remove$b(elem, `${dataAnnotation()}`)
    remove$b(elem, `${dataAnnotationActive()}`)
    const customAttrNames = getOpt(elem, `${dataAnnotationAttributes()}`)
      .map((names) => names.split(','))
      .getOr([])
    const customClasses = getOpt(elem, `${dataAnnotationClasses()}`)
      .map((names) => names.split(','))
      .getOr([])
    each$f(customAttrNames, (name) => remove$b(elem, name))
    remove$5(elem, customClasses)
    remove$b(elem, `${dataAnnotationClasses()}`)
    remove$b(elem, `${dataAnnotationAttributes()}`)
  }
  const makeAnnotation = (eDoc, uid, data, annotationName, decorate) => {
    const master = SugarElement.fromTag('span', eDoc)
    applyAnnotation(master, uid, data, annotationName, decorate, false)
    return master
  }
  const annotate = (editor, rng, uid, annotationName, decorate, data) => {
    const newWrappers = []
    const master = makeAnnotation(editor.getDoc(), uid, data, annotationName, decorate)
    const wrapper = value$2()
    const finishWrapper = () => {
      wrapper.clear()
    }
    const getOrOpenWrapper = () =>
      wrapper.get().getOrThunk(() => {
        const nu = shallow$1(master)
        newWrappers.push(nu)
        wrapper.set(nu)
        return nu
      })
    const processElements = (elems) => {
      each$f(elems, processElement)
    }
    const processElement = (elem) => {
      const ctx = context(editor, elem, 'span', name(elem))
      switch (ctx) {
        case 'invalid-child': {
          finishWrapper()
          const children$1 = children(elem)
          processElements(children$1)
          finishWrapper()
          break
        }
        case 'valid-block': {
          finishWrapper()
          applyAnnotation(elem, uid, data, annotationName, decorate, true)
          break
        }
        case 'valid': {
          const w = getOrOpenWrapper()
          wrap$2(elem, w)
          break
        }
      }
    }
    const processNodes = (nodes) => {
      const elems = map$3(nodes, SugarElement.fromDom)
      processElements(elems)
    }
    walk$3(editor.dom, rng, (nodes) => {
      finishWrapper()
      processNodes(nodes)
    })
    return newWrappers
  }
  const annotateWithBookmark = (editor, name, settings, data) => {
    editor.undoManager.transact(() => {
      const selection = editor.selection
      const initialRng = selection.getRng()
      const hasFakeSelection = getCellsFromEditor(editor).length > 0
      const masterUid = generate$1('mce-annotation')
      if (initialRng.collapsed && !hasFakeSelection) {
        applyWordGrab(editor, initialRng)
      }
      if (selection.getRng().collapsed && !hasFakeSelection) {
        const wrapper = makeAnnotation(editor.getDoc(), masterUid, data, name, settings.decorate)
        set(wrapper, nbsp)
        selection.getRng().insertNode(wrapper.dom)
        selection.select(wrapper.dom)
      } else {
        preserve(selection, false, () => {
          runOnRanges(editor, (selectionRng) => {
            annotate(editor, selectionRng, masterUid, name, settings.decorate, data)
          })
        })
      }
    })
  }

  const Annotator = (editor) => {
    const registry = create$c()
    setup$w(editor, registry)
    const changes = setup$x(editor, registry)
    const isSpan = isTag('span')
    const removeAnnotations = (elements) => {
      each$f(elements, (element) => {
        if (isSpan(element)) {
          unwrap(element)
        } else {
          removeDirectAnnotation(element)
        }
      })
    }
    return {
      register: (name, settings) => {
        registry.register(name, settings)
      },
      annotate: (name, data) => {
        registry.lookup(name).each((settings) => {
          annotateWithBookmark(editor, name, settings, data)
        })
      },
      annotationChanged: (name, callback) => {
        changes.addListener(name, callback)
      },
      remove: (name) => {
        const bookmark = editor.selection.getBookmark()
        identify(editor, Optional.some(name)).each(({ elements }) => {
          removeAnnotations(elements)
        })
        editor.selection.moveToBookmark(bookmark)
      },
      removeAll: (name) => {
        const bookmark = editor.selection.getBookmark()
        each$e(findAll(editor, name), (elements, _) => {
          removeAnnotations(elements)
        })
        editor.selection.moveToBookmark(bookmark)
      },
      getAll: (name) => {
        const directory = findAll(editor, name)
        return map$2(directory, (elems) => map$3(elems, (elem) => elem.dom))
      },
    }
  }

  const BookmarkManager = (selection) => {
    return {
      getBookmark: curry(getBookmark$1, selection),
      moveToBookmark: curry(moveToBookmark, selection),
    }
  }
  BookmarkManager.isBookmarkNode = isBookmarkNode$1

  const isXYWithinRange = (clientX, clientY, range) => {
    if (range.collapsed) {
      return false
    } else {
      return exists(range.getClientRects(), (rect) => containsXY(rect, clientX, clientY))
    }
  }

  const firePreProcess = (editor, args) => editor.dispatch('PreProcess', args)
  const firePostProcess = (editor, args) => editor.dispatch('PostProcess', args)
  const fireRemove = (editor) => editor.dispatch('remove')
  const fireDetach = (editor) => editor.dispatch('detach')
  const fireSwitchMode = (editor, mode) => editor.dispatch('SwitchMode', { mode })
  const fireObjectResizeStart = (editor, target, width, height, origin) => {
    editor.dispatch('ObjectResizeStart', {
      target,
      width,
      height,
      origin,
    })
  }
  const fireObjectResized = (editor, target, width, height, origin) => {
    editor.dispatch('ObjectResized', {
      target,
      width,
      height,
      origin,
    })
  }
  const firePreInit = (editor) => editor.dispatch('PreInit')
  const firePostRender = (editor) => editor.dispatch('PostRender')
  const fireInit = (editor) => editor.dispatch('Init')
  const firePlaceholderToggle = (editor, state) => editor.dispatch('PlaceholderToggle', { state })
  const fireError = (editor, errorType, error) => editor.dispatch(errorType, error)
  const fireFormatApply = (editor, format, node, vars) =>
    editor.dispatch('FormatApply', {
      format,
      node,
      vars,
    })
  const fireFormatRemove = (editor, format, node, vars) =>
    editor.dispatch('FormatRemove', {
      format,
      node,
      vars,
    })
  const fireBeforeSetContent = (editor, args) => editor.dispatch('BeforeSetContent', args)
  const fireSetContent = (editor, args) => editor.dispatch('SetContent', args)
  const fireBeforeGetContent = (editor, args) => editor.dispatch('BeforeGetContent', args)
  const fireGetContent = (editor, args) => editor.dispatch('GetContent', args)
  const fireAutocompleterStart = (editor, args) => editor.dispatch('AutocompleterStart', args)
  const fireAutocompleterUpdate = (editor, args) => editor.dispatch('AutocompleterUpdate', args)
  const fireAutocompleterEnd = (editor) => editor.dispatch('AutocompleterEnd')
  const firePastePreProcess = (editor, html, internal) =>
    editor.dispatch('PastePreProcess', {
      content: html,
      internal,
    })
  const firePastePostProcess = (editor, node, internal) =>
    editor.dispatch('PastePostProcess', {
      node,
      internal,
    })
  const firePastePlainTextToggle = (editor, state) => editor.dispatch('PastePlainTextToggle', { state })

  const VK = {
    BACKSPACE: 8,
    DELETE: 46,
    DOWN: 40,
    ENTER: 13,
    ESC: 27,
    LEFT: 37,
    RIGHT: 39,
    SPACEBAR: 32,
    TAB: 9,
    UP: 38,
    PAGE_UP: 33,
    PAGE_DOWN: 34,
    END: 35,
    HOME: 36,
    modifierPressed: (e) => {
      return e.shiftKey || e.ctrlKey || e.altKey || VK.metaKeyPressed(e)
    },
    metaKeyPressed: (e) => {
      return Env.os.isMacOS() || Env.os.isiOS() ? e.metaKey : e.ctrlKey && !e.altKey
    },
  }

  const elementSelectionAttr = 'data-mce-selected'
  const controlElmSelector = 'table,img,figure.image,hr,video,span.mce-preview-object'
  const abs = Math.abs
  const round$1 = Math.round
  const resizeHandles = {
    nw: [0, 0, -1, -1],
    ne: [1, 0, 1, -1],
    se: [1, 1, 1, 1],
    sw: [0, 1, -1, 1],
  }
  const isTouchEvent = (evt) => evt.type === 'longpress' || evt.type.indexOf('touch') === 0
  const ControlSelection = (selection, editor) => {
    const dom = editor.dom
    const editableDoc = editor.getDoc()
    const rootDocument = document
    const rootElement = editor.getBody()
    let selectedElm, selectedElmGhost, resizeHelper, selectedHandle, resizeBackdrop
    let startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted
    let width
    let height
    let startScrollWidth
    let startScrollHeight
    const isImage = (elm) => isNonNullable(elm) && (isImg(elm) || dom.is(elm, 'figure.image'))
    const isMedia = (elm) => isMedia$2(elm) || dom.hasClass(elm, 'mce-preview-object')
    const isEventOnImageOutsideRange = (evt, range) => {
      if (isTouchEvent(evt)) {
        const touch = evt.touches[0]
        return isImage(evt.target) && !isXYWithinRange(touch.clientX, touch.clientY, range)
      } else {
        return isImage(evt.target) && !isXYWithinRange(evt.clientX, evt.clientY, range)
      }
    }
    const contextMenuSelectImage = (evt) => {
      const target = evt.target
      if (isEventOnImageOutsideRange(evt, editor.selection.getRng()) && !evt.isDefaultPrevented()) {
        editor.selection.select(target)
      }
    }
    const getResizeTargets = (elm) => {
      if (dom.is(elm, 'figure.image')) {
        return [elm.querySelector('img')]
      } else if (dom.hasClass(elm, 'mce-preview-object') && isNonNullable(elm.firstElementChild)) {
        return [elm, elm.firstElementChild]
      } else {
        return [elm]
      }
    }
    const isResizable = (elm) => {
      const selector = getObjectResizing(editor)
      if (!selector) {
        return false
      }
      if (elm.getAttribute('data-mce-resize') === 'false') {
        return false
      }
      if (elm === editor.getBody()) {
        return false
      }
      if (dom.hasClass(elm, 'mce-preview-object')) {
        return is$1(SugarElement.fromDom(elm.firstElementChild), selector)
      } else {
        return is$1(SugarElement.fromDom(elm), selector)
      }
    }
    const createGhostElement = (elm) => {
      if (isMedia(elm)) {
        return dom.create('img', { src: Env.transparentSrc })
      } else {
        return elm.cloneNode(true)
      }
    }
    const setSizeProp = (element, name, value) => {
      if (isNonNullable(value)) {
        const targets = getResizeTargets(element)
        each$f(targets, (target) => {
          if (target.style[name] || !editor.schema.isValid(target.nodeName.toLowerCase(), name)) {
            dom.setStyle(target, name, value)
          } else {
            dom.setAttrib(target, name, '' + value)
          }
        })
      }
    }
    const setGhostElmSize = (ghostElm, width, height) => {
      setSizeProp(ghostElm, 'width', width)
      setSizeProp(ghostElm, 'height', height)
    }
    const resizeGhostElement = (e) => {
      let deltaX, deltaY, proportional
      let resizeHelperX, resizeHelperY
      deltaX = e.screenX - startX
      deltaY = e.screenY - startY
      width = deltaX * selectedHandle[2] + startW
      height = deltaY * selectedHandle[3] + startH
      width = width < 5 ? 5 : width
      height = height < 5 ? 5 : height
      if ((isImage(selectedElm) || isMedia(selectedElm)) && getResizeImgProportional(editor) !== false) {
        proportional = !VK.modifierPressed(e)
      } else {
        proportional = VK.modifierPressed(e)
      }
      if (proportional) {
        if (abs(deltaX) > abs(deltaY)) {
          height = round$1(width * ratio)
          width = round$1(height / ratio)
        } else {
          width = round$1(height / ratio)
          height = round$1(width * ratio)
        }
      }
      setGhostElmSize(selectedElmGhost, width, height)
      resizeHelperX = selectedHandle.startPos.x + deltaX
      resizeHelperY = selectedHandle.startPos.y + deltaY
      resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0
      resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0
      dom.setStyles(resizeHelper, {
        left: resizeHelperX,
        top: resizeHelperY,
        display: 'block',
      })
      resizeHelper.innerHTML = width + ' &times; ' + height
      if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
        dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width))
      }
      if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
        dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height))
      }
      deltaX = rootElement.scrollWidth - startScrollWidth
      deltaY = rootElement.scrollHeight - startScrollHeight
      if (deltaX + deltaY !== 0) {
        dom.setStyles(resizeHelper, {
          left: resizeHelperX - deltaX,
          top: resizeHelperY - deltaY,
        })
      }
      if (!resizeStarted) {
        fireObjectResizeStart(editor, selectedElm, startW, startH, 'corner-' + selectedHandle.name)
        resizeStarted = true
      }
    }
    const endGhostResize = () => {
      const wasResizeStarted = resizeStarted
      resizeStarted = false
      if (wasResizeStarted) {
        setSizeProp(selectedElm, 'width', width)
        setSizeProp(selectedElm, 'height', height)
      }
      dom.unbind(editableDoc, 'mousemove', resizeGhostElement)
      dom.unbind(editableDoc, 'mouseup', endGhostResize)
      if (rootDocument !== editableDoc) {
        dom.unbind(rootDocument, 'mousemove', resizeGhostElement)
        dom.unbind(rootDocument, 'mouseup', endGhostResize)
      }
      dom.remove(selectedElmGhost)
      dom.remove(resizeHelper)
      dom.remove(resizeBackdrop)
      showResizeRect(selectedElm)
      if (wasResizeStarted) {
        fireObjectResized(editor, selectedElm, width, height, 'corner-' + selectedHandle.name)
        dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'))
      }
      editor.nodeChanged()
    }
    const showResizeRect = (targetElm) => {
      unbindResizeHandleEvents()
      const position = dom.getPos(targetElm, rootElement)
      const selectedElmX = position.x
      const selectedElmY = position.y
      const rect = targetElm.getBoundingClientRect()
      const targetWidth = rect.width || rect.right - rect.left
      const targetHeight = rect.height || rect.bottom - rect.top
      if (selectedElm !== targetElm) {
        hideResizeRect()
        selectedElm = targetElm
        width = height = 0
      }
      const e = editor.dispatch('ObjectSelected', { target: targetElm })
      if (isResizable(targetElm) && !e.isDefaultPrevented()) {
        each$e(resizeHandles, (handle, name) => {
          let handleElm
          const startDrag = (e) => {
            const target = getResizeTargets(selectedElm)[0]
            startX = e.screenX
            startY = e.screenY
            startW = target.clientWidth
            startH = target.clientHeight
            ratio = startH / startW
            selectedHandle = handle
            selectedHandle.name = name
            selectedHandle.startPos = {
              x: targetWidth * handle[0] + selectedElmX,
              y: targetHeight * handle[1] + selectedElmY,
            }
            startScrollWidth = rootElement.scrollWidth
            startScrollHeight = rootElement.scrollHeight
            resizeBackdrop = dom.add(rootElement, 'div', {
              'class': 'mce-resize-backdrop',
              'data-mce-bogus': 'all',
            })
            dom.setStyles(resizeBackdrop, {
              position: 'fixed',
              left: '0',
              top: '0',
              width: '100%',
              height: '100%',
            })
            selectedElmGhost = createGhostElement(selectedElm)
            dom.addClass(selectedElmGhost, 'mce-clonedresizable')
            dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all')
            selectedElmGhost.contentEditable = 'false'
            dom.setStyles(selectedElmGhost, {
              left: selectedElmX,
              top: selectedElmY,
              margin: 0,
            })
            setGhostElmSize(selectedElmGhost, targetWidth, targetHeight)
            selectedElmGhost.removeAttribute(elementSelectionAttr)
            rootElement.appendChild(selectedElmGhost)
            dom.bind(editableDoc, 'mousemove', resizeGhostElement)
            dom.bind(editableDoc, 'mouseup', endGhostResize)
            if (rootDocument !== editableDoc) {
              dom.bind(rootDocument, 'mousemove', resizeGhostElement)
              dom.bind(rootDocument, 'mouseup', endGhostResize)
            }
            resizeHelper = dom.add(
              rootElement,
              'div',
              {
                'class': 'mce-resize-helper',
                'data-mce-bogus': 'all',
              },
              startW + ' &times; ' + startH
            )
          }
          handleElm = dom.get('mceResizeHandle' + name)
          if (handleElm) {
            dom.remove(handleElm)
          }
          handleElm = dom.add(rootElement, 'div', {
            'id': 'mceResizeHandle' + name,
            'data-mce-bogus': 'all',
            'class': 'mce-resizehandle',
            'unselectable': true,
            'style': 'cursor:' + name + '-resize; margin:0; padding:0',
          })
          dom.bind(handleElm, 'mousedown', (e) => {
            e.stopImmediatePropagation()
            e.preventDefault()
            startDrag(e)
          })
          handle.elm = handleElm
          dom.setStyles(handleElm, {
            left: targetWidth * handle[0] + selectedElmX - handleElm.offsetWidth / 2,
            top: targetHeight * handle[1] + selectedElmY - handleElm.offsetHeight / 2,
          })
        })
      } else {
        hideResizeRect(false)
      }
    }
    const throttledShowResizeRect = first$1(showResizeRect, 0)
    const hideResizeRect = (removeSelected = true) => {
      throttledShowResizeRect.cancel()
      unbindResizeHandleEvents()
      if (selectedElm && removeSelected) {
        selectedElm.removeAttribute(elementSelectionAttr)
      }
      each$e(resizeHandles, (value, name) => {
        const handleElm = dom.get('mceResizeHandle' + name)
        if (handleElm) {
          dom.unbind(handleElm)
          dom.remove(handleElm)
        }
      })
    }
    const isChildOrEqual = (node, parent) => isNonNullable(node) && dom.isChildOf(node, parent)
    const updateResizeRect = (e) => {
      if (resizeStarted || editor.removed || editor.composing) {
        return
      }
      const targetElm = e.type === 'mousedown' ? e.target : selection.getNode()
      const controlElm = closest$3(SugarElement.fromDom(targetElm), controlElmSelector)
        .map((e) => e.dom)
        .getOrUndefined()
      const selectedValue = isNonNullable(controlElm) ? dom.getAttrib(controlElm, elementSelectionAttr, '1') : '1'
      each$f(dom.select(`img[${elementSelectionAttr}],hr[${elementSelectionAttr}]`), (img) => {
        img.removeAttribute(elementSelectionAttr)
      })
      if (isChildOrEqual(controlElm, rootElement)) {
        disableGeckoResize()
        const startElm = selection.getStart(true)
        if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
          dom.setAttrib(controlElm, elementSelectionAttr, selectedValue)
          throttledShowResizeRect.throttle(controlElm)
          return
        }
      }
      hideResizeRect()
    }
    const unbindResizeHandleEvents = () => {
      each$e(resizeHandles, (handle) => {
        if (handle.elm) {
          dom.unbind(handle.elm)
          delete handle.elm
        }
      })
    }
    const disableGeckoResize = () => {
      try {
        editor.getDoc().execCommand('enableObjectResizing', false, 'false')
      } catch (ex) {}
    }
    editor.on('init', () => {
      disableGeckoResize()
      editor.on('NodeChange ResizeEditor ResizeWindow ResizeContent drop', updateResizeRect)
      editor.on('keyup compositionend', (e) => {
        if (selectedElm && selectedElm.nodeName === 'TABLE') {
          updateResizeRect(e)
        }
      })
      editor.on('hide blur', hideResizeRect)
      editor.on('contextmenu longpress', contextMenuSelectImage, true)
    })
    editor.on('remove', unbindResizeHandleEvents)
    const destroy = () => {
      throttledShowResizeRect.cancel()
      selectedElm = selectedElmGhost = resizeBackdrop = null
    }
    return {
      isResizable,
      showResizeRect,
      hideResizeRect,
      updateResizeRect,
      destroy,
    }
  }

  const setStart = (rng, situ) => {
    situ.fold(
      (e) => {
        rng.setStartBefore(e.dom)
      },
      (e, o) => {
        rng.setStart(e.dom, o)
      },
      (e) => {
        rng.setStartAfter(e.dom)
      }
    )
  }
  const setFinish = (rng, situ) => {
    situ.fold(
      (e) => {
        rng.setEndBefore(e.dom)
      },
      (e, o) => {
        rng.setEnd(e.dom, o)
      },
      (e) => {
        rng.setEndAfter(e.dom)
      }
    )
  }
  const relativeToNative = (win, startSitu, finishSitu) => {
    const range = win.document.createRange()
    setStart(range, startSitu)
    setFinish(range, finishSitu)
    return range
  }
  const exactToNative = (win, start, soffset, finish, foffset) => {
    const rng = win.document.createRange()
    rng.setStart(start.dom, soffset)
    rng.setEnd(finish.dom, foffset)
    return rng
  }

  const adt$3 = Adt.generate([
    {
      ltr: ['start', 'soffset', 'finish', 'foffset'],
    },
    {
      rtl: ['start', 'soffset', 'finish', 'foffset'],
    },
  ])
  const fromRange = (win, type, range) =>
    type(
      SugarElement.fromDom(range.startContainer),
      range.startOffset,
      SugarElement.fromDom(range.endContainer),
      range.endOffset
    )
  const getRanges = (win, selection) =>
    selection.match({
      domRange: (rng) => {
        return {
          ltr: constant(rng),
          rtl: Optional.none,
        }
      },
      relative: (startSitu, finishSitu) => {
        return {
          ltr: cached(() => relativeToNative(win, startSitu, finishSitu)),
          rtl: cached(() => Optional.some(relativeToNative(win, finishSitu, startSitu))),
        }
      },
      exact: (start, soffset, finish, foffset) => {
        return {
          ltr: cached(() => exactToNative(win, start, soffset, finish, foffset)),
          rtl: cached(() => Optional.some(exactToNative(win, finish, foffset, start, soffset))),
        }
      },
    })
  const doDiagnose = (win, ranges) => {
    const rng = ranges.ltr()
    if (rng.collapsed) {
      const reversed = ranges.rtl().filter((rev) => rev.collapsed === false)
      return reversed
        .map((rev) =>
          adt$3.rtl(
            SugarElement.fromDom(rev.endContainer),
            rev.endOffset,
            SugarElement.fromDom(rev.startContainer),
            rev.startOffset
          )
        )
        .getOrThunk(() => fromRange(win, adt$3.ltr, rng))
    } else {
      return fromRange(win, adt$3.ltr, rng)
    }
  }
  const diagnose = (win, selection) => {
    const ranges = getRanges(win, selection)
    return doDiagnose(win, ranges)
  }
  adt$3.ltr
  adt$3.rtl

  const create$a = (start, soffset, finish, foffset) => ({
    start,
    soffset,
    finish,
    foffset,
  })
  const SimRange = { create: create$a }

  const caretPositionFromPoint = (doc, x, y) => {
    var _a, _b
    return Optional.from(
      (_b = (_a = doc.dom).caretPositionFromPoint) === null || _b === void 0 ? void 0 : _b.call(_a, x, y)
    ).bind((pos) => {
      if (pos.offsetNode === null) {
        return Optional.none()
      }
      const r = doc.dom.createRange()
      r.setStart(pos.offsetNode, pos.offset)
      r.collapse()
      return Optional.some(r)
    })
  }
  const caretRangeFromPoint = (doc, x, y) => {
    var _a, _b
    return Optional.from(
      (_b = (_a = doc.dom).caretRangeFromPoint) === null || _b === void 0 ? void 0 : _b.call(_a, x, y)
    )
  }
  const availableSearch = (() => {
    if (document.caretPositionFromPoint) {
      return caretPositionFromPoint
    } else if (document.caretRangeFromPoint) {
      return caretRangeFromPoint
    } else {
      return Optional.none
    }
  })()
  const fromPoint$1 = (win, x, y) => {
    const doc = SugarElement.fromDom(win.document)
    return availableSearch(doc, x, y).map((rng) =>
      SimRange.create(
        SugarElement.fromDom(rng.startContainer),
        rng.startOffset,
        SugarElement.fromDom(rng.endContainer),
        rng.endOffset
      )
    )
  }

  const adt$2 = Adt.generate([
    { before: ['element'] },
    {
      on: ['element', 'offset'],
    },
    { after: ['element'] },
  ])
  const cata = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, onAfter)
  const getStart$2 = (situ) => situ.fold(identity, identity, identity)
  const before$1 = adt$2.before
  const on = adt$2.on
  const after$1 = adt$2.after
  const Situ = {
    before: before$1,
    on,
    after: after$1,
    cata,
    getStart: getStart$2,
  }

  const adt$1 = Adt.generate([
    { domRange: ['rng'] },
    {
      relative: ['startSitu', 'finishSitu'],
    },
    {
      exact: ['start', 'soffset', 'finish', 'foffset'],
    },
  ])
  const exactFromRange = (simRange) => adt$1.exact(simRange.start, simRange.soffset, simRange.finish, simRange.foffset)
  const getStart$1 = (selection) =>
    selection.match({
      domRange: (rng) => SugarElement.fromDom(rng.startContainer),
      relative: (startSitu, _finishSitu) => Situ.getStart(startSitu),
      exact: (start, _soffset, _finish, _foffset) => start,
    })
  const domRange = adt$1.domRange
  const relative = adt$1.relative
  const exact = adt$1.exact
  const getWin = (selection) => {
    const start = getStart$1(selection)
    return defaultView(start)
  }
  const range = SimRange.create
  const SimSelection = {
    domRange,
    relative,
    exact,
    exactFromRange,
    getWin,
    range,
  }

  const beforeSpecial = (element, offset) => {
    const name$1 = name(element)
    if ('input' === name$1) {
      return Situ.after(element)
    } else if (!contains$2(['br', 'img'], name$1)) {
      return Situ.on(element, offset)
    } else {
      return offset === 0 ? Situ.before(element) : Situ.after(element)
    }
  }
  const preprocessRelative = (startSitu, finishSitu) => {
    const start = startSitu.fold(Situ.before, beforeSpecial, Situ.after)
    const finish = finishSitu.fold(Situ.before, beforeSpecial, Situ.after)
    return SimSelection.relative(start, finish)
  }
  const preprocessExact = (start, soffset, finish, foffset) => {
    const startSitu = beforeSpecial(start, soffset)
    const finishSitu = beforeSpecial(finish, foffset)
    return SimSelection.relative(startSitu, finishSitu)
  }
  const preprocess = (selection) =>
    selection.match({
      domRange: (rng) => {
        const start = SugarElement.fromDom(rng.startContainer)
        const finish = SugarElement.fromDom(rng.endContainer)
        return preprocessExact(start, rng.startOffset, finish, rng.endOffset)
      },
      relative: preprocessRelative,
      exact: preprocessExact,
    })

  const fromElements = (elements, scope) => {
    const doc = scope || document
    const fragment = doc.createDocumentFragment()
    each$f(elements, (element) => {
      fragment.appendChild(element.dom)
    })
    return SugarElement.fromDom(fragment)
  }

  const toNative = (selection) => {
    const win = SimSelection.getWin(selection).dom
    const getDomRange = (start, soffset, finish, foffset) => exactToNative(win, start, soffset, finish, foffset)
    const filtered = preprocess(selection)
    return diagnose(win, filtered).match({
      ltr: getDomRange,
      rtl: getDomRange,
    })
  }
  const getAtPoint = (win, x, y) => fromPoint$1(win, x, y)

  const fromPoint = (clientX, clientY, doc) =>
    getAtPoint(doc.defaultView, clientX, clientY)
      .map((simRange) => {
        const rng = doc.createRange()
        rng.setStart(simRange.start.dom, simRange.soffset)
        rng.setEnd(simRange.finish.dom, simRange.foffset)
        return rng
      })
      .getOrUndefined()

  const isEq$4 = (rng1, rng2) => {
    return (
      rng1 &&
      rng2 &&
      rng1.startContainer === rng2.startContainer &&
      rng1.startOffset === rng2.startOffset &&
      rng1.endContainer === rng2.endContainer &&
      rng1.endOffset === rng2.endOffset
    )
  }

  const findParent = (node, rootNode, predicate) => {
    while (node && node !== rootNode) {
      if (predicate(node)) {
        return node
      }
      node = node.parentNode
    }
    return null
  }
  const hasParent$1 = (node, rootNode, predicate) => findParent(node, rootNode, predicate) !== null
  const hasParentWithName = (node, rootNode, name) =>
    hasParent$1(node, rootNode, (node) => {
      return node.nodeName === name
    })
  const isTable = (node) => node && node.nodeName === 'TABLE'
  const isTableCell$2 = (node) => node && /^(TD|TH|CAPTION)$/.test(node.nodeName)
  const isCeFalseCaretContainer = (node, rootNode) =>
    isCaretContainer$2(node) && hasParent$1(node, rootNode, isCaretNode) === false
  const hasBrBeforeAfter = (dom, node, left) => {
    const walker = new DomTreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || dom.getRoot())
    while ((node = walker[left ? 'prev' : 'next']())) {
      if (isBr$6(node)) {
        return true
      }
    }
  }
  const isPrevNode = (node, name) => node.previousSibling && node.previousSibling.nodeName === name
  const hasContentEditableFalseParent = (body, node) => {
    while (node && node !== body) {
      if (isContentEditableFalse$b(node)) {
        return true
      }
      node = node.parentNode
    }
    return false
  }
  const findTextNodeRelative = (dom, isAfterNode, collapsed, left, startNode) => {
    let lastInlineElement
    const body = dom.getRoot()
    let node
    const nonEmptyElementsMap = dom.schema.getNonEmptyElements()
    const parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body
    if (left && isBr$6(startNode) && isAfterNode && dom.isEmpty(parentBlockContainer)) {
      return Optional.some(CaretPosition(startNode.parentNode, dom.nodeIndex(startNode)))
    }
    const walker = new DomTreeWalker(startNode, parentBlockContainer)
    while ((node = walker[left ? 'prev' : 'next']())) {
      if (dom.getContentEditableParent(node) === 'false' || isCeFalseCaretContainer(node, body)) {
        return Optional.none()
      }
      if (isText$9(node) && node.nodeValue.length > 0) {
        if (hasParentWithName(node, body, 'A') === false) {
          return Optional.some(CaretPosition(node, left ? node.nodeValue.length : 0))
        }
        return Optional.none()
      }
      if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
        return Optional.none()
      }
      lastInlineElement = node
    }
    if (isComment(lastInlineElement)) {
      return Optional.none()
    }
    if (collapsed && lastInlineElement) {
      return Optional.some(CaretPosition(lastInlineElement, 0))
    }
    return Optional.none()
  }
  const normalizeEndPoint = (dom, collapsed, start, rng) => {
    let container, offset
    const body = dom.getRoot()
    let node
    let directionLeft,
      normalized = false
    container = rng[(start ? 'start' : 'end') + 'Container']
    offset = rng[(start ? 'start' : 'end') + 'Offset']
    const isAfterNode = isElement$6(container) && offset === container.childNodes.length
    const nonEmptyElementsMap = dom.schema.getNonEmptyElements()
    directionLeft = start
    if (isCaretContainer$2(container)) {
      return Optional.none()
    }
    if (isElement$6(container) && offset > container.childNodes.length - 1) {
      directionLeft = false
    }
    if (isDocument$1(container)) {
      container = body
      offset = 0
    }
    if (container === body) {
      if (directionLeft) {
        node = container.childNodes[offset > 0 ? offset - 1 : 0]
        if (node) {
          if (isCaretContainer$2(node)) {
            return Optional.none()
          }
          if (nonEmptyElementsMap[node.nodeName] || isTable(node)) {
            return Optional.none()
          }
        }
      }
      if (container.hasChildNodes()) {
        offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)
        container = container.childNodes[offset]
        offset = isText$9(container) && isAfterNode ? container.data.length : 0
        if (!collapsed && container === body.lastChild && isTable(container)) {
          return Optional.none()
        }
        if (hasContentEditableFalseParent(body, container) || isCaretContainer$2(container)) {
          return Optional.none()
        }
        if (container.hasChildNodes() && isTable(container) === false) {
          node = container
          const walker = new DomTreeWalker(container, body)
          do {
            if (isContentEditableFalse$b(node) || isCaretContainer$2(node)) {
              normalized = false
              break
            }
            if (isText$9(node) && node.nodeValue.length > 0) {
              offset = directionLeft ? 0 : node.nodeValue.length
              container = node
              normalized = true
              break
            }
            if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell$2(node)) {
              offset = dom.nodeIndex(node)
              container = node.parentNode
              if (!directionLeft) {
                offset++
              }
              normalized = true
              break
            }
          } while ((node = directionLeft ? walker.next() : walker.prev()))
        }
      }
    }
    if (collapsed) {
      if (isText$9(container) && offset === 0) {
        findTextNodeRelative(dom, isAfterNode, collapsed, true, container).each((pos) => {
          container = pos.container()
          offset = pos.offset()
          normalized = true
        })
      }
      if (isElement$6(container)) {
        node = container.childNodes[offset]
        if (!node) {
          node = container.childNodes[offset - 1]
        }
        if (
          node &&
          isBr$6(node) &&
          !isPrevNode(node, 'A') &&
          !hasBrBeforeAfter(dom, node, false) &&
          !hasBrBeforeAfter(dom, node, true)
        ) {
          findTextNodeRelative(dom, isAfterNode, collapsed, true, node).each((pos) => {
            container = pos.container()
            offset = pos.offset()
            normalized = true
          })
        }
      }
    }
    if (directionLeft && !collapsed && isText$9(container) && offset === container.nodeValue.length) {
      findTextNodeRelative(dom, isAfterNode, collapsed, false, container).each((pos) => {
        container = pos.container()
        offset = pos.offset()
        normalized = true
      })
    }
    return normalized ? Optional.some(CaretPosition(container, offset)) : Optional.none()
  }
  const normalize$2 = (dom, rng) => {
    const collapsed = rng.collapsed,
      normRng = rng.cloneRange()
    const startPos = CaretPosition.fromRangeStart(rng)
    normalizeEndPoint(dom, collapsed, true, normRng).each((pos) => {
      if (!collapsed || !CaretPosition.isAbove(startPos, pos)) {
        normRng.setStart(pos.container(), pos.offset())
      }
    })
    if (!collapsed) {
      normalizeEndPoint(dom, collapsed, false, normRng).each((pos) => {
        normRng.setEnd(pos.container(), pos.offset())
      })
    }
    if (collapsed) {
      normRng.collapse(true)
    }
    return isEq$4(rng, normRng) ? Optional.none() : Optional.some(normRng)
  }

  const splitText = (node, offset) => {
    return node.splitText(offset)
  }
  const split = (rng) => {
    let startContainer = rng.startContainer,
      startOffset = rng.startOffset,
      endContainer = rng.endContainer,
      endOffset = rng.endOffset
    if (startContainer === endContainer && isText$9(startContainer)) {
      if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
        endContainer = splitText(startContainer, startOffset)
        startContainer = endContainer.previousSibling
        if (endOffset > startOffset) {
          endOffset = endOffset - startOffset
          startContainer = endContainer = splitText(endContainer, endOffset).previousSibling
          endOffset = endContainer.nodeValue.length
          startOffset = 0
        } else {
          endOffset = 0
        }
      }
    } else {
      if (isText$9(startContainer) && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
        startContainer = splitText(startContainer, startOffset)
        startOffset = 0
      }
      if (isText$9(endContainer) && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
        endContainer = splitText(endContainer, endOffset).previousSibling
        endOffset = endContainer.nodeValue.length
      }
    }
    return {
      startContainer,
      startOffset,
      endContainer,
      endOffset,
    }
  }

  const RangeUtils = (dom) => {
    const walk = (rng, callback) => {
      return walk$3(dom, rng, callback)
    }
    const split$1 = split
    const normalize = (rng) => {
      return normalize$2(dom, rng).fold(never, (normalizedRng) => {
        rng.setStart(normalizedRng.startContainer, normalizedRng.startOffset)
        rng.setEnd(normalizedRng.endContainer, normalizedRng.endOffset)
        return true
      })
    }
    return {
      walk,
      split: split$1,
      normalize,
    }
  }
  RangeUtils.compareRanges = isEq$4
  RangeUtils.getCaretRangeFromPoint = fromPoint
  RangeUtils.getSelectedNode = getSelectedNode
  RangeUtils.getNode = getNode$1

  const Dimension = (name, getOffset) => {
    const set = (element, h) => {
      if (!isNumber(h) && !h.match(/^[0-9]+$/)) {
        throw new Error(name + '.set accepts only positive integer values. Value was ' + h)
      }
      const dom = element.dom
      if (isSupported$1(dom)) {
        dom.style[name] = h + 'px'
      }
    }
    const get = (element) => {
      const r = getOffset(element)
      if (r <= 0 || r === null) {
        const css = get$7(element, name)
        return parseFloat(css) || 0
      }
      return r
    }
    const getOuter = get
    const aggregate = (element, properties) =>
      foldl(
        properties,
        (acc, property) => {
          const val = get$7(element, property)
          const value = val === undefined ? 0 : parseInt(val, 10)
          return isNaN(value) ? acc : acc + value
        },
        0
      )
    const max = (element, value, properties) => {
      const cumulativeInclusions = aggregate(element, properties)
      const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0
      return absoluteMax
    }
    return {
      set,
      get,
      getOuter,
      aggregate,
      max,
    }
  }

  const api = Dimension('height', (element) => {
    const dom = element.dom
    return inBody(element) ? dom.getBoundingClientRect().height : dom.offsetHeight
  })
  const get$2 = (element) => api.get(element)

  const getDocument = () => SugarElement.fromDom(document)

  const walkUp = (navigation, doc) => {
    const frame = navigation.view(doc)
    return frame.fold(constant([]), (f) => {
      const parent = navigation.owner(f)
      const rest = walkUp(navigation, parent)
      return [f].concat(rest)
    })
  }
  const pathTo = (element, navigation) => {
    const d = navigation.owner(element)
    return walkUp(navigation, d)
  }

  const view = (doc) => {
    var _a
    const element =
      doc.dom === document
        ? Optional.none()
        : Optional.from((_a = doc.dom.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement)
    return element.map(SugarElement.fromDom)
  }
  const owner = (element) => documentOrOwner(element)

  var Navigation = /*#__PURE__*/ Object.freeze({
    __proto__: null,
    view: view,
    owner: owner,
  })

  const find = (element) => {
    const doc = getDocument()
    const scroll = get$5(doc)
    const frames = pathTo(element, Navigation)
    const offset = viewport(element)
    const r = foldr(
      frames,
      (b, a) => {
        const loc = viewport(a)
        return {
          left: b.left + loc.left,
          top: b.top + loc.top,
        }
      },
      {
        left: 0,
        top: 0,
      }
    )
    return SugarPosition(r.left + offset.left + scroll.left, r.top + offset.top + scroll.top)
  }

  const excludeFromDescend = (element) => name(element) === 'textarea'
  const fireScrollIntoViewEvent = (editor, data) => {
    const scrollEvent = editor.dispatch('ScrollIntoView', data)
    return scrollEvent.isDefaultPrevented()
  }
  const fireAfterScrollIntoViewEvent = (editor, data) => {
    editor.dispatch('AfterScrollIntoView', data)
  }
  const descend = (element, offset) => {
    const children$1 = children(element)
    if (children$1.length === 0 || excludeFromDescend(element)) {
      return {
        element,
        offset,
      }
    } else if (offset < children$1.length && !excludeFromDescend(children$1[offset])) {
      return {
        element: children$1[offset],
        offset: 0,
      }
    } else {
      const last = children$1[children$1.length - 1]
      if (excludeFromDescend(last)) {
        return {
          element,
          offset,
        }
      } else {
        if (name(last) === 'img') {
          return {
            element: last,
            offset: 1,
          }
        } else if (isText$a(last)) {
          return {
            element: last,
            offset: get$3(last).length,
          }
        } else {
          return {
            element: last,
            offset: children(last).length,
          }
        }
      }
    }
  }
  const markerInfo = (element, cleanupFun) => {
    const pos = absolute(element)
    const height = get$2(element)
    return {
      element,
      bottom: pos.top + height,
      height,
      pos,
      cleanup: cleanupFun,
    }
  }
  const createMarker$1 = (element, offset) => {
    const startPoint = descend(element, offset)
    const span = SugarElement.fromHtml(
      '<span data-mce-bogus="all" style="display: inline-block;">' + ZWSP$1 + '</span>'
    )
    before$3(startPoint.element, span)
    return markerInfo(span, () => remove$6(span))
  }
  const elementMarker = (element) => markerInfo(SugarElement.fromDom(element), noop)
  const withMarker = (editor, f, rng, alignToTop) => {
    preserveWith(editor, (_s, _e) => applyWithMarker(editor, f, rng, alignToTop), rng)
  }
  const withScrollEvents = (editor, doc, f, marker, alignToTop) => {
    const data = {
      elm: marker.element.dom,
      alignToTop,
    }
    if (fireScrollIntoViewEvent(editor, data)) {
      return
    }
    const scrollTop = get$5(doc).top
    f(doc, scrollTop, marker, alignToTop)
    fireAfterScrollIntoViewEvent(editor, data)
  }
  const applyWithMarker = (editor, f, rng, alignToTop) => {
    const body = SugarElement.fromDom(editor.getBody())
    const doc = SugarElement.fromDom(editor.getDoc())
    reflow(body)
    const marker = createMarker$1(SugarElement.fromDom(rng.startContainer), rng.startOffset)
    withScrollEvents(editor, doc, f, marker, alignToTop)
    marker.cleanup()
  }
  const withElement = (editor, element, f, alignToTop) => {
    const doc = SugarElement.fromDom(editor.getDoc())
    withScrollEvents(editor, doc, f, elementMarker(element), alignToTop)
  }
  const preserveWith = (editor, f, rng) => {
    const startElement = rng.startContainer
    const startOffset = rng.startOffset
    const endElement = rng.endContainer
    const endOffset = rng.endOffset
    f(SugarElement.fromDom(startElement), SugarElement.fromDom(endElement))
    const newRng = editor.dom.createRng()
    newRng.setStart(startElement, startOffset)
    newRng.setEnd(endElement, endOffset)
    editor.selection.setRng(rng)
  }
  const scrollToMarker = (marker, viewHeight, alignToTop, doc) => {
    const pos = marker.pos
    if (alignToTop) {
      to(pos.left, pos.top, doc)
    } else {
      const y = pos.top - viewHeight + marker.height
      to(pos.left, y, doc)
    }
  }
  const intoWindowIfNeeded = (doc, scrollTop, viewHeight, marker, alignToTop) => {
    const viewportBottom = viewHeight + scrollTop
    const markerTop = marker.pos.top
    const markerBottom = marker.bottom
    const largerThanViewport = markerBottom - markerTop >= viewHeight
    if (markerTop < scrollTop) {
      scrollToMarker(marker, viewHeight, alignToTop !== false, doc)
    } else if (markerTop > viewportBottom) {
      const align = largerThanViewport ? alignToTop !== false : alignToTop === true
      scrollToMarker(marker, viewHeight, align, doc)
    } else if (markerBottom > viewportBottom && !largerThanViewport) {
      scrollToMarker(marker, viewHeight, alignToTop === true, doc)
    }
  }
  const intoWindow = (doc, scrollTop, marker, alignToTop) => {
    const viewHeight = doc.dom.defaultView.innerHeight
    intoWindowIfNeeded(doc, scrollTop, viewHeight, marker, alignToTop)
  }
  const intoFrame = (doc, scrollTop, marker, alignToTop) => {
    const frameViewHeight = doc.dom.defaultView.innerHeight
    intoWindowIfNeeded(doc, scrollTop, frameViewHeight, marker, alignToTop)
    const op = find(marker.element)
    const viewportBounds = getBounds(window)
    if (op.top < viewportBounds.y) {
      intoView(marker.element, alignToTop !== false)
    } else if (op.top > viewportBounds.bottom) {
      intoView(marker.element, alignToTop === true)
    }
  }
  const rangeIntoWindow = (editor, rng, alignToTop) => withMarker(editor, intoWindow, rng, alignToTop)
  const elementIntoWindow = (editor, element, alignToTop) => withElement(editor, element, intoWindow, alignToTop)
  const rangeIntoFrame = (editor, rng, alignToTop) => withMarker(editor, intoFrame, rng, alignToTop)
  const elementIntoFrame = (editor, element, alignToTop) => withElement(editor, element, intoFrame, alignToTop)
  const scrollElementIntoView = (editor, element, alignToTop) => {
    const scroller = editor.inline ? elementIntoWindow : elementIntoFrame
    scroller(editor, element, alignToTop)
  }
  const scrollRangeIntoView = (editor, rng, alignToTop) => {
    const scroller = editor.inline ? rangeIntoWindow : rangeIntoFrame
    scroller(editor, rng, alignToTop)
  }

  const focus$1 = (element) => element.dom.focus()
  const hasFocus$1 = (element) => {
    const root = getRootNode(element).dom
    return element.dom === root.activeElement
  }
  const active$1 = (root = getDocument()) => Optional.from(root.dom.activeElement).map(SugarElement.fromDom)
  const search = (element) => active$1(getRootNode(element)).filter((e) => element.dom.contains(e.dom))

  const clamp$1 = (offset, element) => {
    const max = isText$a(element) ? get$3(element).length : children(element).length + 1
    if (offset > max) {
      return max
    } else if (offset < 0) {
      return 0
    }
    return offset
  }
  const normalizeRng = (rng) =>
    SimSelection.range(rng.start, clamp$1(rng.soffset, rng.start), rng.finish, clamp$1(rng.foffset, rng.finish))
  const isOrContains = (root, elm) => !isRestrictedNode(elm.dom) && (contains(root, elm) || eq(root, elm))
  const isRngInRoot = (root) => (rng) => isOrContains(root, rng.start) && isOrContains(root, rng.finish)
  const shouldStore = (editor) => editor.inline
  const nativeRangeToSelectionRange = (r) =>
    SimSelection.range(
      SugarElement.fromDom(r.startContainer),
      r.startOffset,
      SugarElement.fromDom(r.endContainer),
      r.endOffset
    )
  const readRange = (win) => {
    const selection = win.getSelection()
    const rng = !selection || selection.rangeCount === 0 ? Optional.none() : Optional.from(selection.getRangeAt(0))
    return rng.map(nativeRangeToSelectionRange)
  }
  const getBookmark = (root) => {
    const win = defaultView(root)
    return readRange(win.dom).filter(isRngInRoot(root))
  }
  const validate = (root, bookmark) => Optional.from(bookmark).filter(isRngInRoot(root)).map(normalizeRng)
  const bookmarkToNativeRng = (bookmark) => {
    const rng = document.createRange()
    try {
      rng.setStart(bookmark.start.dom, bookmark.soffset)
      rng.setEnd(bookmark.finish.dom, bookmark.foffset)
      return Optional.some(rng)
    } catch (_) {
      return Optional.none()
    }
  }
  const store = (editor) => {
    const newBookmark = shouldStore(editor) ? getBookmark(SugarElement.fromDom(editor.getBody())) : Optional.none()
    editor.bookmark = newBookmark.isSome() ? newBookmark : editor.bookmark
  }
  const getRng = (editor) => {
    const bookmark = editor.bookmark ? editor.bookmark : Optional.none()
    return bookmark.bind((x) => validate(SugarElement.fromDom(editor.getBody()), x)).bind(bookmarkToNativeRng)
  }
  const restore = (editor) => {
    getRng(editor).each((rng) => editor.selection.setRng(rng))
  }

  const isEditorUIElement$1 = (elm) => {
    const className = elm.className.toString()
    return className.indexOf('tox-') !== -1 || className.indexOf('mce-') !== -1
  }
  const FocusManager = { isEditorUIElement: isEditorUIElement$1 }

  const wrappedSetTimeout = (callback, time) => {
    if (!isNumber(time)) {
      time = 0
    }
    return setTimeout(callback, time)
  }
  const wrappedSetInterval = (callback, time) => {
    if (!isNumber(time)) {
      time = 0
    }
    return setInterval(callback, time)
  }
  const Delay = {
    setEditorTimeout: (editor, callback, time) => {
      return wrappedSetTimeout(() => {
        if (!editor.removed) {
          callback()
        }
      }, time)
    },
    setEditorInterval: (editor, callback, time) => {
      const timer = wrappedSetInterval(() => {
        if (!editor.removed) {
          callback()
        } else {
          clearInterval(timer)
        }
      }, time)
      return timer
    },
  }

  const isManualNodeChange = (e) => {
    return e.type === 'nodechange' && e.selectionChange
  }
  const registerPageMouseUp = (editor, throttledStore) => {
    const mouseUpPage = () => {
      throttledStore.throttle()
    }
    DOMUtils.DOM.bind(document, 'mouseup', mouseUpPage)
    editor.on('remove', () => {
      DOMUtils.DOM.unbind(document, 'mouseup', mouseUpPage)
    })
  }
  const registerMouseUp = (editor, throttledStore) => {
    editor.on('mouseup touchend', (_e) => {
      throttledStore.throttle()
    })
  }
  const registerEditorEvents = (editor, throttledStore) => {
    registerMouseUp(editor, throttledStore)
    editor.on('keyup NodeChange AfterSetSelectionRange', (e) => {
      if (!isManualNodeChange(e)) {
        store(editor)
      }
    })
  }
  const register$6 = (editor) => {
    const throttledStore = first$1(() => {
      store(editor)
    }, 0)
    editor.on('init', () => {
      if (editor.inline) {
        registerPageMouseUp(editor, throttledStore)
      }
      registerEditorEvents(editor, throttledStore)
    })
    editor.on('remove', () => {
      throttledStore.cancel()
    })
  }

  let documentFocusInHandler
  const DOM$9 = DOMUtils.DOM
  const isEditorUIElement = (elm) => {
    return FocusManager.isEditorUIElement(elm)
  }
  const isEditorContentAreaElement = (elm) => {
    const classList = elm.classList
    if (classList !== undefined) {
      return (
        classList.contains('tox-edit-area') ||
        classList.contains('tox-edit-area__iframe') ||
        classList.contains('mce-content-body')
      )
    } else {
      return false
    }
  }
  const isUIElement = (editor, elm) => {
    const customSelector = getCustomUiSelector(editor)
    const parent = DOM$9.getParent(elm, (elm) => {
      return isEditorUIElement(elm) || (customSelector ? editor.dom.is(elm, customSelector) : false)
    })
    return parent !== null
  }
  const getActiveElement = (editor) => {
    try {
      const root = getRootNode(SugarElement.fromDom(editor.getElement()))
      return active$1(root).fold(
        () => document.body,
        (x) => x.dom
      )
    } catch (ex) {
      return document.body
    }
  }
  const registerEvents$1 = (editorManager, e) => {
    const editor = e.editor
    register$6(editor)
    editor.on('focusin', () => {
      const focusedEditor = editorManager.focusedEditor
      if (focusedEditor !== editor) {
        if (focusedEditor) {
          focusedEditor.dispatch('blur', { focusedEditor: editor })
        }
        editorManager.setActive(editor)
        editorManager.focusedEditor = editor
        editor.dispatch('focus', { blurredEditor: focusedEditor })
        editor.focus(true)
      }
    })
    editor.on('focusout', () => {
      Delay.setEditorTimeout(editor, () => {
        const focusedEditor = editorManager.focusedEditor
        if (!isUIElement(editor, getActiveElement(editor)) && focusedEditor === editor) {
          editor.dispatch('blur', { focusedEditor: null })
          editorManager.focusedEditor = null
        }
      })
    })
    if (!documentFocusInHandler) {
      documentFocusInHandler = (e) => {
        const activeEditor = editorManager.activeEditor
        if (activeEditor) {
          getOriginalEventTarget(e).each((target) => {
            if (target.ownerDocument === document) {
              if (
                target !== document.body &&
                !isUIElement(activeEditor, target) &&
                editorManager.focusedEditor === activeEditor
              ) {
                activeEditor.dispatch('blur', { focusedEditor: null })
                editorManager.focusedEditor = null
              }
            }
          })
        }
      }
      DOM$9.bind(document, 'focusin', documentFocusInHandler)
    }
  }
  const unregisterDocumentEvents = (editorManager, e) => {
    if (editorManager.focusedEditor === e.editor) {
      editorManager.focusedEditor = null
    }
    if (!editorManager.activeEditor) {
      DOM$9.unbind(document, 'focusin', documentFocusInHandler)
      documentFocusInHandler = null
    }
  }
  const setup$v = (editorManager) => {
    editorManager.on('AddEditor', curry(registerEvents$1, editorManager))
    editorManager.on('RemoveEditor', curry(unregisterDocumentEvents, editorManager))
  }

  const getContentEditableHost = (editor, node) =>
    editor.dom.getParent(node, (node) => editor.dom.getContentEditable(node) === 'true')
  const getCollapsedNode = (rng) =>
    rng.collapsed
      ? Optional.from(getNode$1(rng.startContainer, rng.startOffset)).map(SugarElement.fromDom)
      : Optional.none()
  const getFocusInElement = (root, rng) =>
    getCollapsedNode(rng).bind((node) => {
      if (isTableSection(node)) {
        return Optional.some(node)
      } else if (contains(root, node) === false) {
        return Optional.some(root)
      } else {
        return Optional.none()
      }
    })
  const normalizeSelection = (editor, rng) => {
    getFocusInElement(SugarElement.fromDom(editor.getBody()), rng)
      .bind((elm) => {
        return firstPositionIn(elm.dom)
      })
      .fold(
        () => {
          editor.selection.normalize()
          return
        },
        (caretPos) => editor.selection.setRng(caretPos.toRange())
      )
  }
  const focusBody = (body) => {
    if (body.setActive) {
      try {
        body.setActive()
      } catch (ex) {
        body.focus()
      }
    } else {
      body.focus()
    }
  }
  const hasElementFocus = (elm) => hasFocus$1(elm) || search(elm).isSome()
  const hasIframeFocus = (editor) => editor.iframeElement && hasFocus$1(SugarElement.fromDom(editor.iframeElement))
  const hasInlineFocus = (editor) => {
    const rawBody = editor.getBody()
    return rawBody && hasElementFocus(SugarElement.fromDom(rawBody))
  }
  const hasUiFocus = (editor) => {
    const dos = getRootNode(SugarElement.fromDom(editor.getElement()))
    return active$1(dos)
      .filter((elem) => !isEditorContentAreaElement(elem.dom) && isUIElement(editor, elem.dom))
      .isSome()
  }
  const hasFocus = (editor) => (editor.inline ? hasInlineFocus(editor) : hasIframeFocus(editor))
  const hasEditorOrUiFocus = (editor) => hasFocus(editor) || hasUiFocus(editor)
  const focusEditor = (editor) => {
    const selection = editor.selection
    const body = editor.getBody()
    let rng = selection.getRng()
    editor.quirks.refreshContentEditable()
    if (editor.bookmark !== undefined && hasFocus(editor) === false) {
      getRng(editor).each((bookmarkRng) => {
        editor.selection.setRng(bookmarkRng)
        rng = bookmarkRng
      })
    }
    const contentEditableHost = getContentEditableHost(editor, selection.getNode())
    if (editor.dom.isChildOf(contentEditableHost, body)) {
      focusBody(contentEditableHost)
      normalizeSelection(editor, rng)
      activateEditor(editor)
      return
    }
    if (!editor.inline) {
      if (!Env.browser.isOpera()) {
        focusBody(body)
      }
      editor.getWin().focus()
    }
    if (Env.browser.isFirefox() || editor.inline) {
      focusBody(body)
      normalizeSelection(editor, rng)
    }
    activateEditor(editor)
  }
  const activateEditor = (editor) => editor.editorManager.setActive(editor)
  const focus = (editor, skipFocus) => {
    if (editor.removed) {
      return
    }
    if (skipFocus) {
      activateEditor(editor)
    } else {
      focusEditor(editor)
    }
  }

  const getEndpointElement = (root, rng, start, real, resolve) => {
    const container = start ? rng.startContainer : rng.endContainer
    const offset = start ? rng.startOffset : rng.endOffset
    return Optional.from(container)
      .map(SugarElement.fromDom)
      .map((elm) => (!real || !rng.collapsed ? child$1(elm, resolve(elm, offset)).getOr(elm) : elm))
      .bind((elm) => (isElement$7(elm) ? Optional.some(elm) : parent(elm).filter(isElement$7)))
      .map((elm) => elm.dom)
      .getOr(root)
  }
  const getStart = (root, rng, real) =>
    getEndpointElement(root, rng, true, real, (elm, offset) => Math.min(childNodesCount(elm), offset))
  const getEnd$1 = (root, rng, real) =>
    getEndpointElement(root, rng, false, real, (elm, offset) => (offset > 0 ? offset - 1 : offset))
  const skipEmptyTextNodes = (node, forwards) => {
    const orig = node
    while (node && isText$9(node) && node.length === 0) {
      node = forwards ? node.nextSibling : node.previousSibling
    }
    return node || orig
  }
  const getNode = (root, rng) => {
    let elm, startContainer, endContainer
    if (!rng) {
      return root
    }
    startContainer = rng.startContainer
    endContainer = rng.endContainer
    const startOffset = rng.startOffset
    const endOffset = rng.endOffset
    elm = rng.commonAncestorContainer
    if (!rng.collapsed) {
      if (startContainer === endContainer) {
        if (endOffset - startOffset < 2) {
          if (startContainer.hasChildNodes()) {
            elm = startContainer.childNodes[startOffset]
          }
        }
      }
      if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
        if (startContainer.length === startOffset) {
          startContainer = skipEmptyTextNodes(startContainer.nextSibling, true)
        } else {
          startContainer = startContainer.parentNode
        }
        if (endOffset === 0) {
          endContainer = skipEmptyTextNodes(endContainer.previousSibling, false)
        } else {
          endContainer = endContainer.parentNode
        }
        if (startContainer && startContainer === endContainer) {
          return startContainer
        }
      }
    }
    if (elm && elm.nodeType === 3) {
      return elm.parentNode
    }
    return elm
  }
  const getSelectedBlocks = (dom, rng, startElm, endElm) => {
    let node
    const selectedBlocks = []
    const root = dom.getRoot()
    startElm = dom.getParent(startElm || getStart(root, rng, rng.collapsed), dom.isBlock)
    endElm = dom.getParent(endElm || getEnd$1(root, rng, rng.collapsed), dom.isBlock)
    if (startElm && startElm !== root) {
      selectedBlocks.push(startElm)
    }
    if (startElm && endElm && startElm !== endElm) {
      node = startElm
      const walker = new DomTreeWalker(startElm, root)
      while ((node = walker.next()) && node !== endElm) {
        if (dom.isBlock(node)) {
          selectedBlocks.push(node)
        }
      }
    }
    if (endElm && startElm !== endElm && endElm !== root) {
      selectedBlocks.push(endElm)
    }
    return selectedBlocks
  }
  const select = (dom, node, content) =>
    Optional.from(node).map((node) => {
      const idx = dom.nodeIndex(node)
      const rng = dom.createRng()
      rng.setStart(node.parentNode, idx)
      rng.setEnd(node.parentNode, idx + 1)
      if (content) {
        moveEndPoint(dom, rng, node, true)
        moveEndPoint(dom, rng, node, false)
      }
      return rng
    })

  const processRanges = (editor, ranges) =>
    map$3(ranges, (range) => {
      const evt = editor.dispatch('GetSelectionRange', { range })
      return evt.range !== range ? evt.range : range
    })

  const getEnd = (element) =>
    name(element) === 'img'
      ? 1
      : getOption(element).fold(
          () => children(element).length,
          (v) => v.length
        )
  const isTextNodeWithCursorPosition = (el) =>
    getOption(el)
      .filter((text) => text.trim().length !== 0 || text.indexOf(nbsp) > -1)
      .isSome()
  const elementsWithCursorPosition = ['img', 'br']
  const isCursorPosition = (elem) => {
    const hasCursorPosition = isTextNodeWithCursorPosition(elem)
    return hasCursorPosition || contains$2(elementsWithCursorPosition, name(elem))
  }

  const first = (element) => descendant$1(element, isCursorPosition)
  const last = (element) => descendantRtl(element, isCursorPosition)
  const descendantRtl = (scope, predicate) => {
    const descend = (element) => {
      const children$1 = children(element)
      for (let i = children$1.length - 1; i >= 0; i--) {
        const child = children$1[i]
        if (predicate(child)) {
          return Optional.some(child)
        }
        const res = descend(child)
        if (res.isSome()) {
          return res
        }
      }
      return Optional.none()
    }
    return descend(scope)
  }

  const autocompleteSelector = '[data-mce-autocompleter]'
  const create$9 = (editor, range) => {
    if (findIn(SugarElement.fromDom(editor.getBody())).isNone()) {
      const wrapper = SugarElement.fromHtml(
        '<span data-mce-autocompleter="1" data-mce-bogus="1"></span>',
        editor.getDoc()
      )
      append$1(wrapper, SugarElement.fromDom(range.extractContents()))
      range.insertNode(wrapper.dom)
      parent(wrapper).each((elm) => elm.dom.normalize())
      last(wrapper).map((last) => {
        editor.selection.setCursorLocation(last.dom, getEnd(last))
      })
    }
  }
  const detect$1 = (elm) => closest$3(elm, autocompleteSelector)
  const findIn = (elm) => descendant(elm, autocompleteSelector)
  const remove$3 = (editor, elm) =>
    findIn(elm).each((wrapper) => {
      const bookmark = editor.selection.getBookmark()
      unwrap(wrapper)
      editor.selection.moveToBookmark(bookmark)
    })

  const typeLookup = {
    '#text': 3,
    '#comment': 8,
    '#cdata': 4,
    '#pi': 7,
    '#doctype': 10,
    '#document-fragment': 11,
  }
  const walk$2 = (node, root, prev) => {
    const startName = prev ? 'lastChild' : 'firstChild'
    const siblingName = prev ? 'prev' : 'next'
    if (node[startName]) {
      return node[startName]
    }
    if (node !== root) {
      let sibling = node[siblingName]
      if (sibling) {
        return sibling
      }
      for (let parent = node.parent; parent && parent !== root; parent = parent.parent) {
        sibling = parent[siblingName]
        if (sibling) {
          return sibling
        }
      }
    }
  }
  const isEmptyTextNode = (node) => {
    if (!isWhitespaceText(node.value)) {
      return false
    }
    const parentNode = node.parent
    if (parentNode && (parentNode.name !== 'span' || parentNode.attr('style')) && /^[ ]+$/.test(node.value)) {
      return false
    }
    return true
  }
  const isNonEmptyElement = (node) => {
    const isNamedAnchor = node.name === 'a' && !node.attr('href') && node.attr('id')
    return node.attr('name') || (node.attr('id') && !node.firstChild) || node.attr('data-mce-bookmark') || isNamedAnchor
  }
  class AstNode {
    constructor(name, type) {
      this.name = name
      this.type = type
      if (type === 1) {
        this.attributes = []
        this.attributes.map = {}
      }
    }
    static create(name, attrs) {
      const node = new AstNode(name, typeLookup[name] || 1)
      if (attrs) {
        each$e(attrs, (value, attrName) => {
          node.attr(attrName, value)
        })
      }
      return node
    }
    replace(node) {
      const self = this
      if (node.parent) {
        node.remove()
      }
      self.insert(node, self)
      self.remove()
      return self
    }
    attr(name, value) {
      const self = this
      let attrs
      if (typeof name !== 'string') {
        if (name !== undefined && name !== null) {
          each$e(name, (value, key) => {
            self.attr(key, value)
          })
        }
        return self
      }
      if ((attrs = self.attributes)) {
        if (value !== undefined) {
          if (value === null) {
            if (name in attrs.map) {
              delete attrs.map[name]
              let i = attrs.length
              while (i--) {
                if (attrs[i].name === name) {
                  attrs.splice(i, 1)
                  return self
                }
              }
            }
            return self
          }
          if (name in attrs.map) {
            let i = attrs.length
            while (i--) {
              if (attrs[i].name === name) {
                attrs[i].value = value
                break
              }
            }
          } else {
            attrs.push({
              name,
              value,
            })
          }
          attrs.map[name] = value
          return self
        }
        return attrs.map[name]
      }
    }
    clone() {
      const self = this
      const clone = new AstNode(self.name, self.type)
      let selfAttrs
      if ((selfAttrs = self.attributes)) {
        const cloneAttrs = []
        cloneAttrs.map = {}
        for (let i = 0, l = selfAttrs.length; i < l; i++) {
          const selfAttr = selfAttrs[i]
          if (selfAttr.name !== 'id') {
            cloneAttrs[cloneAttrs.length] = {
              name: selfAttr.name,
              value: selfAttr.value,
            }
            cloneAttrs.map[selfAttr.name] = selfAttr.value
          }
        }
        clone.attributes = cloneAttrs
      }
      clone.value = self.value
      return clone
    }
    wrap(wrapper) {
      const self = this
      self.parent.insert(wrapper, self)
      wrapper.append(self)
      return self
    }
    unwrap() {
      const self = this
      for (let node = self.firstChild; node; ) {
        const next = node.next
        self.insert(node, self, true)
        node = next
      }
      self.remove()
    }
    remove() {
      const self = this,
        parent = self.parent,
        next = self.next,
        prev = self.prev
      if (parent) {
        if (parent.firstChild === self) {
          parent.firstChild = next
          if (next) {
            next.prev = null
          }
        } else {
          prev.next = next
        }
        if (parent.lastChild === self) {
          parent.lastChild = prev
          if (prev) {
            prev.next = null
          }
        } else {
          next.prev = prev
        }
        self.parent = self.next = self.prev = null
      }
      return self
    }
    append(node) {
      const self = this
      if (node.parent) {
        node.remove()
      }
      const last = self.lastChild
      if (last) {
        last.next = node
        node.prev = last
        self.lastChild = node
      } else {
        self.lastChild = self.firstChild = node
      }
      node.parent = self
      return node
    }
    insert(node, refNode, before) {
      if (node.parent) {
        node.remove()
      }
      const parent = refNode.parent || this
      if (before) {
        if (refNode === parent.firstChild) {
          parent.firstChild = node
        } else {
          refNode.prev.next = node
        }
        node.prev = refNode.prev
        node.next = refNode
        refNode.prev = node
      } else {
        if (refNode === parent.lastChild) {
          parent.lastChild = node
        } else {
          refNode.next.prev = node
        }
        node.next = refNode.next
        node.prev = refNode
        refNode.next = node
      }
      node.parent = parent
      return node
    }
    getAll(name) {
      const self = this
      const collection = []
      for (let node = self.firstChild; node; node = walk$2(node, self)) {
        if (node.name === name) {
          collection.push(node)
        }
      }
      return collection
    }
    children() {
      const self = this
      const collection = []
      for (let node = self.firstChild; node; node = node.next) {
        collection.push(node)
      }
      return collection
    }
    empty() {
      const self = this
      if (self.firstChild) {
        const nodes = []
        for (let node = self.firstChild; node; node = walk$2(node, self)) {
          nodes.push(node)
        }
        let i = nodes.length
        while (i--) {
          const node = nodes[i]
          node.parent = node.firstChild = node.lastChild = node.next = node.prev = null
        }
      }
      self.firstChild = self.lastChild = null
      return self
    }
    isEmpty(elements, whitespace = {}, predicate) {
      const self = this
      let node = self.firstChild
      if (isNonEmptyElement(self)) {
        return false
      }
      if (node) {
        do {
          if (node.type === 1) {
            if (node.attr('data-mce-bogus')) {
              continue
            }
            if (elements[node.name]) {
              return false
            }
            if (isNonEmptyElement(node)) {
              return false
            }
          }
          if (node.type === 8) {
            return false
          }
          if (node.type === 3 && !isEmptyTextNode(node)) {
            return false
          }
          if (node.type === 3 && node.parent && whitespace[node.parent.name] && isWhitespaceText(node.value)) {
            return false
          }
          if (predicate && predicate(node)) {
            return false
          }
        } while ((node = walk$2(node, self)))
      }
      return true
    }
    walk(prev) {
      return walk$2(this, null, prev)
    }
  }

  const isConditionalComment = (html, startIndex) =>
    /^\s*\[if [\w\W]+\]>.*<!\[endif\](--!?)?>/.test(html.substr(startIndex))
  const findCommentEndIndex = (html, isBogus, startIndex = 0) => {
    const lcHtml = html.toLowerCase()
    if (lcHtml.indexOf('[if ', startIndex) !== -1 && isConditionalComment(lcHtml, startIndex)) {
      const endIfIndex = lcHtml.indexOf('[endif]', startIndex)
      return lcHtml.indexOf('>', endIfIndex)
    } else {
      if (isBogus) {
        const endIndex = lcHtml.indexOf('>', startIndex)
        return endIndex !== -1 ? endIndex : lcHtml.length
      } else {
        const endCommentRegexp = /--!?>/g
        endCommentRegexp.lastIndex = startIndex
        const match = endCommentRegexp.exec(html)
        return match ? match.index + match[0].length : lcHtml.length
      }
    }
  }
  const findMatchingEndTagIndex = (schema, html, startIndex) => {
    const startTagRegExp = /<([!?\/])?([A-Za-z0-9\-_:.]+)/g
    const endTagRegExp = /(?:\s(?:[^'">]+(?:"[^"]*"|'[^']*'))*[^"'>]*(?:"[^">]*|'[^'>]*)?|\s*|\/)>/g
    const voidElements = schema.getVoidElements()
    let count = 1,
      index = startIndex
    while (count !== 0) {
      startTagRegExp.lastIndex = index
      while (true) {
        const startMatch = startTagRegExp.exec(html)
        if (startMatch === null) {
          return index
        } else if (startMatch[1] === '!') {
          if (startsWith(startMatch[2], '--')) {
            index = findCommentEndIndex(html, false, startMatch.index + '!--'.length)
          } else {
            index = findCommentEndIndex(html, true, startMatch.index + 1)
          }
          break
        } else {
          endTagRegExp.lastIndex = startTagRegExp.lastIndex
          const endMatch = endTagRegExp.exec(html)
          if (isNull(endMatch) || endMatch.index !== startTagRegExp.lastIndex) {
            continue
          }
          if (startMatch[1] === '/') {
            count -= 1
          } else if (!has$2(voidElements, startMatch[2])) {
            count += 1
          }
          index = startTagRegExp.lastIndex + endMatch[0].length
          break
        }
      }
    }
    return index
  }
  const trimHtml$1 = (tempAttrs, html) => {
    const trimContentRegExp = new RegExp(['\\s?(' + tempAttrs.join('|') + ')="[^"]+"'].join('|'), 'gi')
    return html.replace(trimContentRegExp, '')
  }
  const trimInternal = (serializer, html) => {
    const bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g
    const schema = serializer.schema
    let content = trimHtml$1(serializer.getTempAttrs(), html)
    const voidElements = schema.getVoidElements()
    let matches
    while ((matches = bogusAllRegExp.exec(content))) {
      const index = bogusAllRegExp.lastIndex
      const matchLength = matches[0].length
      let endTagIndex
      if (voidElements[matches[1]]) {
        endTagIndex = index
      } else {
        endTagIndex = findMatchingEndTagIndex(schema, content, index)
      }
      content = content.substring(0, index - matchLength) + content.substring(endTagIndex)
      bogusAllRegExp.lastIndex = index - matchLength
    }
    return trim$1(content)
  }
  const trimExternal = trimInternal

  const trimEmptyContents = (editor, html) => {
    const blockName = getForcedRootBlock(editor)
    const emptyRegExp = new RegExp(
      `^(<${blockName}[^>]*>(&nbsp;|&#160;|\\s|\u00a0|<br \\/>|)<\\/${blockName}>[\r\n]*|<br \\/>[\r\n]*)$`
    )
    return html.replace(emptyRegExp, '')
  }
  const getContentFromBody = (editor, args, body) => {
    let content
    if (args.format === 'raw') {
      content = Tools.trim(trimExternal(editor.serializer, body.innerHTML))
    } else if (args.format === 'text') {
      content = trim$1(body.innerText)
      content = content === '\n' ? '' : content
    } else if (args.format === 'tree') {
      content = editor.serializer.serialize(body, args)
    } else {
      content = trimEmptyContents(editor, editor.serializer.serialize(body, args))
    }
    const shouldTrim = args.format !== 'text' && !isWsPreserveElement(SugarElement.fromDom(body))
    return shouldTrim && isString(content) ? Tools.trim(content) : content
  }
  const getContentInternal = (editor, args) =>
    Optional.from(editor.getBody()).fold(constant(args.format === 'tree' ? new AstNode('body', 11) : ''), (body) =>
      getContentFromBody(editor, args, body)
    )

  const each$a = Tools.each
  const ElementUtils = (dom) => {
    const compare = (node1, node2) => {
      if (node1.nodeName !== node2.nodeName) {
        return false
      }
      const getAttribs = (node) => {
        const attribs = {}
        each$a(dom.getAttribs(node), (attr) => {
          const name = attr.nodeName.toLowerCase()
          if (name.indexOf('_') !== 0 && name !== 'style' && name.indexOf('data-') !== 0) {
            attribs[name] = dom.getAttrib(node, name)
          }
        })
        return attribs
      }
      const compareObjects = (obj1, obj2) => {
        let value, name
        for (name in obj1) {
          if (has$2(obj1, name)) {
            value = obj2[name]
            if (typeof value === 'undefined') {
              return false
            }
            if (obj1[name] !== value) {
              return false
            }
            delete obj2[name]
          }
        }
        for (name in obj2) {
          if (has$2(obj2, name)) {
            return false
          }
        }
        return true
      }
      if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
        return false
      }
      if (
        !compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))
      ) {
        return false
      }
      return !isBookmarkNode$1(node1) && !isBookmarkNode$1(node2)
    }
    return { compare }
  }

  const makeMap$1 = Tools.makeMap
  const Writer = (settings) => {
    const html = []
    settings = settings || {}
    const indent = settings.indent
    const indentBefore = makeMap$1(settings.indent_before || '')
    const indentAfter = makeMap$1(settings.indent_after || '')
    const encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities)
    const htmlOutput = settings.element_format !== 'xhtml'
    return {
      start: (name, attrs, empty) => {
        let i, l, attr, value
        if (indent && indentBefore[name] && html.length > 0) {
          value = html[html.length - 1]
          if (value.length > 0 && value !== '\n') {
            html.push('\n')
          }
        }
        html.push('<', name)
        if (attrs) {
          for (i = 0, l = attrs.length; i < l; i++) {
            attr = attrs[i]
            html.push(' ', attr.name, '="', encode(attr.value, true), '"')
          }
        }
        if (!empty || htmlOutput) {
          html[html.length] = '>'
        } else {
          html[html.length] = ' />'
        }
        if (empty && indent && indentAfter[name] && html.length > 0) {
          value = html[html.length - 1]
          if (value.length > 0 && value !== '\n') {
            html.push('\n')
          }
        }
      },
      end: (name) => {
        let value
        html.push('</', name, '>')
        if (indent && indentAfter[name] && html.length > 0) {
          value = html[html.length - 1]
          if (value.length > 0 && value !== '\n') {
            html.push('\n')
          }
        }
      },
      text: (text, raw) => {
        if (text.length > 0) {
          html[html.length] = raw ? text : encode(text)
        }
      },
      cdata: (text) => {
        html.push('<![CDATA[', text, ']]>')
      },
      comment: (text) => {
        html.push('<!--', text, '-->')
      },
      pi: (name, text) => {
        if (text) {
          html.push('<?', name, ' ', encode(text), '?>')
        } else {
          html.push('<?', name, '?>')
        }
        if (indent) {
          html.push('\n')
        }
      },
      doctype: (text) => {
        html.push('<!DOCTYPE', text, '>', indent ? '\n' : '')
      },
      reset: () => {
        html.length = 0
      },
      getContent: () => {
        return html.join('').replace(/\n$/, '')
      },
    }
  }

  const HtmlSerializer = (settings, schema = Schema()) => {
    const writer = Writer(settings)
    settings = settings || {}
    settings.validate = 'validate' in settings ? settings.validate : true
    const serialize = (node) => {
      const validate = settings.validate
      const handlers = {
        3: (node) => {
          writer.text(node.value, node.raw)
        },
        8: (node) => {
          writer.comment(node.value)
        },
        7: (node) => {
          writer.pi(node.name, node.value)
        },
        10: (node) => {
          writer.doctype(node.value)
        },
        4: (node) => {
          writer.cdata(node.value)
        },
        11: (node) => {
          if ((node = node.firstChild)) {
            do {
              walk(node)
            } while ((node = node.next))
          }
        },
      }
      writer.reset()
      const walk = (node) => {
        const handler = handlers[node.type]
        if (!handler) {
          const name = node.name
          const isEmpty = name in schema.getVoidElements()
          let attrs = node.attributes
          if (validate && attrs && attrs.length > 1) {
            const sortedAttrs = []
            sortedAttrs.map = {}
            const elementRule = schema.getElementRule(node.name)
            if (elementRule) {
              for (let i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
                const attrName = elementRule.attributesOrder[i]
                if (attrName in attrs.map) {
                  const attrValue = attrs.map[attrName]
                  sortedAttrs.map[attrName] = attrValue
                  sortedAttrs.push({
                    name: attrName,
                    value: attrValue,
                  })
                }
              }
              for (let i = 0, l = attrs.length; i < l; i++) {
                const attrName = attrs[i].name
                if (!(attrName in sortedAttrs.map)) {
                  const attrValue = attrs.map[attrName]
                  sortedAttrs.map[attrName] = attrValue
                  sortedAttrs.push({
                    name: attrName,
                    value: attrValue,
                  })
                }
              }
              attrs = sortedAttrs
            }
          }
          writer.start(name, attrs, isEmpty)
          if (!isEmpty) {
            let child = node.firstChild
            if (child) {
              if ((name === 'pre' || name === 'textarea') && child.type === 3 && child.value[0] === '\n') {
                writer.text('\n', true)
              }
              do {
                walk(child)
              } while ((child = child.next))
            }
            writer.end(name)
          }
        } else {
          handler(node)
        }
      }
      if (node.type === 1 && !settings.inner) {
        walk(node)
      } else if (node.type === 3) {
        handlers[3](node)
      } else {
        handlers[11](node)
      }
      return writer.getContent()
    }
    return { serialize }
  }

  const nonInheritableStyles = new Set()
  ;(() => {
    const nonInheritableStylesArr = [
      'margin',
      'margin-left',
      'margin-right',
      'margin-top',
      'margin-bottom',
      'padding',
      'padding-left',
      'padding-right',
      'padding-top',
      'padding-bottom',
      'border',
      'border-width',
      'border-style',
      'border-color',
      'background',
      'background-attachment',
      'background-clip',
      'background-color',
      'background-image',
      'background-origin',
      'background-position',
      'background-repeat',
      'background-size',
      'float',
      'position',
      'left',
      'right',
      'top',
      'bottom',
      'z-index',
      'display',
      'transform',
      'width',
      'max-width',
      'min-width',
      'height',
      'max-height',
      'min-height',
      'overflow',
      'overflow-x',
      'overflow-y',
      'text-overflow',
      'vertical-align',
      'transition',
      'transition-delay',
      'transition-duration',
      'transition-property',
      'transition-timing-function',
    ]
    each$f(nonInheritableStylesArr, (style) => {
      nonInheritableStyles.add(style)
    })
  })()
  const shorthandStyleProps = ['font', 'text-decoration', 'text-emphasis']
  const getStyleProps = (dom, node) => keys(dom.parseStyle(dom.getAttrib(node, 'style')))
  const isNonInheritableStyle = (style) => nonInheritableStyles.has(style)
  const hasInheritableStyles = (dom, node) => forall(getStyleProps(dom, node), (style) => !isNonInheritableStyle(style))
  const getLonghandStyleProps = (styles) =>
    filter$6(styles, (style) => exists(shorthandStyleProps, (prop) => startsWith(style, prop)))
  const hasStyleConflict = (dom, node, parentNode) => {
    const nodeStyleProps = getStyleProps(dom, node)
    const parentNodeStyleProps = getStyleProps(dom, parentNode)
    const valueMismatch = (prop) => {
      var _a, _b
      const nodeValue = (_a = dom.getStyle(node, prop)) !== null && _a !== void 0 ? _a : ''
      const parentValue = (_b = dom.getStyle(parentNode, prop)) !== null && _b !== void 0 ? _b : ''
      return isNotEmpty(nodeValue) && isNotEmpty(parentValue) && nodeValue !== parentValue
    }
    return exists(nodeStyleProps, (nodeStyleProp) => {
      const propExists = (props) => exists(props, (prop) => prop === nodeStyleProp)
      if (!propExists(parentNodeStyleProps) && propExists(shorthandStyleProps)) {
        const longhandProps = getLonghandStyleProps(parentNodeStyleProps)
        return exists(longhandProps, valueMismatch)
      } else {
        return valueMismatch(nodeStyleProp)
      }
    })
  }

  const isChar = (forward, predicate, pos) =>
    Optional.from(pos.container())
      .filter(isText$9)
      .exists((text) => {
        const delta = forward ? 0 : -1
        return predicate(text.data.charAt(pos.offset() + delta))
      })
  const isBeforeSpace = curry(isChar, true, isWhiteSpace)
  const isAfterSpace = curry(isChar, false, isWhiteSpace)
  const isEmptyText = (pos) => {
    const container = pos.container()
    return (
      isText$9(container) &&
      (container.data.length === 0 || (isZwsp(container.data) && BookmarkManager.isBookmarkNode(container.parentNode)))
    )
  }
  const matchesElementPosition = (before, predicate) => (pos) =>
    Optional.from(getChildNodeAtRelativeOffset(before ? 0 : -1, pos))
      .filter(predicate)
      .isSome()
  const isImageBlock = (node) => isImg(node) && get$7(SugarElement.fromDom(node), 'display') === 'block'
  const isCefNode = (node) => isContentEditableFalse$b(node) && !isBogusAll$1(node)
  const isBeforeImageBlock = matchesElementPosition(true, isImageBlock)
  const isAfterImageBlock = matchesElementPosition(false, isImageBlock)
  const isBeforeMedia = matchesElementPosition(true, isMedia$2)
  const isAfterMedia = matchesElementPosition(false, isMedia$2)
  const isBeforeTable = matchesElementPosition(true, isTable$3)
  const isAfterTable = matchesElementPosition(false, isTable$3)
  const isBeforeContentEditableFalse = matchesElementPosition(true, isCefNode)
  const isAfterContentEditableFalse = matchesElementPosition(false, isCefNode)

  const dropLast = (xs) => xs.slice(0, -1)
  const parentsUntil = (start, root, predicate) => {
    if (contains(root, start)) {
      return dropLast(
        parents$1(start, (elm) => {
          return predicate(elm) || eq(elm, root)
        })
      )
    } else {
      return []
    }
  }
  const parents = (start, root) => parentsUntil(start, root, never)
  const parentsAndSelf = (start, root) => [start].concat(parents(start, root))

  const navigateIgnoreEmptyTextNodes = (forward, root, from) => navigateIgnore(forward, root, from, isEmptyText)
  const getClosestBlock$1 = (root, pos) =>
    find$2(parentsAndSelf(SugarElement.fromDom(pos.container()), root), isBlock$2)
  const isAtBeforeAfterBlockBoundary = (forward, root, pos) =>
    navigateIgnoreEmptyTextNodes(forward, root.dom, pos).forall((newPos) =>
      getClosestBlock$1(root, pos).fold(
        () => isInSameBlock(newPos, pos, root.dom) === false,
        (fromBlock) =>
          isInSameBlock(newPos, pos, root.dom) === false &&
          contains(fromBlock, SugarElement.fromDom(newPos.container()))
      )
    )
  const isAtBlockBoundary = (forward, root, pos) =>
    getClosestBlock$1(root, pos).fold(
      () =>
        navigateIgnoreEmptyTextNodes(forward, root.dom, pos).forall(
          (newPos) => isInSameBlock(newPos, pos, root.dom) === false
        ),
      (parent) => navigateIgnoreEmptyTextNodes(forward, parent.dom, pos).isNone()
    )
  const isAtStartOfBlock = curry(isAtBlockBoundary, false)
  const isAtEndOfBlock = curry(isAtBlockBoundary, true)
  const isBeforeBlock = curry(isAtBeforeAfterBlockBoundary, false)
  const isAfterBlock = curry(isAtBeforeAfterBlockBoundary, true)

  const isBr$1 = (pos) => getElementFromPosition(pos).exists(isBr$5)
  const findBr = (forward, root, pos) => {
    const parentBlocks = filter$6(parentsAndSelf(SugarElement.fromDom(pos.container()), root), isBlock$2)
    const scope = head(parentBlocks).getOr(root)
    return fromPosition(forward, scope.dom, pos).filter(isBr$1)
  }
  const isBeforeBr$1 = (root, pos) => getElementFromPosition(pos).exists(isBr$5) || findBr(true, root, pos).isSome()
  const isAfterBr = (root, pos) => getElementFromPrevPosition(pos).exists(isBr$5) || findBr(false, root, pos).isSome()
  const findPreviousBr = curry(findBr, false)
  const findNextBr = curry(findBr, true)

  const isInMiddleOfText = (pos) => CaretPosition.isTextPosition(pos) && !pos.isAtStart() && !pos.isAtEnd()
  const getClosestBlock = (root, pos) => {
    const parentBlocks = filter$6(parentsAndSelf(SugarElement.fromDom(pos.container()), root), isBlock$2)
    return head(parentBlocks).getOr(root)
  }
  const hasSpaceBefore = (root, pos) => {
    if (isInMiddleOfText(pos)) {
      return isAfterSpace(pos)
    } else {
      return isAfterSpace(pos) || prevPosition(getClosestBlock(root, pos).dom, pos).exists(isAfterSpace)
    }
  }
  const hasSpaceAfter = (root, pos) => {
    if (isInMiddleOfText(pos)) {
      return isBeforeSpace(pos)
    } else {
      return isBeforeSpace(pos) || nextPosition(getClosestBlock(root, pos).dom, pos).exists(isBeforeSpace)
    }
  }
  const isPreValue = (value) => contains$2(['pre', 'pre-wrap'], value)
  const isInPre = (pos) =>
    getElementFromPosition(pos)
      .bind((elm) => closest$4(elm, isElement$7))
      .exists((elm) => isPreValue(get$7(elm, 'white-space')))
  const isAtBeginningOfBody = (root, pos) => prevPosition(root.dom, pos).isNone()
  const isAtEndOfBody = (root, pos) => nextPosition(root.dom, pos).isNone()
  const isAtLineBoundary = (root, pos) =>
    isAtBeginningOfBody(root, pos) ||
    isAtEndOfBody(root, pos) ||
    isAtStartOfBlock(root, pos) ||
    isAtEndOfBlock(root, pos) ||
    isAfterBr(root, pos) ||
    isBeforeBr$1(root, pos)
  const isCefBlock = (node) => isNonNullable(node) && isContentEditableFalse$b(node) && isBlockLike(node)
  const isSiblingCefBlock = (root) => (container) => isCefBlock(new DomTreeWalker(container, root).next())
  const isBeforeCefBlock = (root, pos) => {
    const nextPos = nextPosition(root.dom, pos).getOr(pos)
    const isNextCefBlock = isSiblingCefBlock(root.dom)
    return pos.isAtEnd() && (isNextCefBlock(pos.container()) || isNextCefBlock(nextPos.container()))
  }
  const needsToHaveNbsp = (root, pos) => {
    if (isInPre(pos)) {
      return false
    } else {
      return isAtLineBoundary(root, pos) || hasSpaceBefore(root, pos) || hasSpaceAfter(root, pos)
    }
  }
  const needsToBeNbspLeft = (root, pos) => {
    if (isInPre(pos)) {
      return false
    } else {
      return (
        isAtStartOfBlock(root, pos) || isBeforeBlock(root, pos) || isAfterBr(root, pos) || hasSpaceBefore(root, pos)
      )
    }
  }
  const leanRight = (pos) => {
    const container = pos.container()
    const offset = pos.offset()
    if (isText$9(container) && offset < container.data.length) {
      return CaretPosition(container, offset + 1)
    } else {
      return pos
    }
  }
  const needsToBeNbspRight = (root, pos) => {
    if (isInPre(pos)) {
      return false
    } else {
      return (
        isAtEndOfBlock(root, pos) ||
        isAfterBlock(root, pos) ||
        isBeforeBr$1(root, pos) ||
        hasSpaceAfter(root, pos) ||
        isBeforeCefBlock(root, pos)
      )
    }
  }
  const needsToBeNbsp = (root, pos) => needsToBeNbspLeft(root, pos) || needsToBeNbspRight(root, leanRight(pos))
  const isNbspAt = (text, offset) => isNbsp(text.charAt(offset))
  const hasNbsp = (pos) => {
    const container = pos.container()
    return isText$9(container) && contains$1(container.data, nbsp)
  }
  const normalizeNbspMiddle = (text) => {
    const chars = text.split('')
    return map$3(chars, (chr, i) => {
      if (isNbsp(chr) && i > 0 && i < chars.length - 1 && isContent(chars[i - 1]) && isContent(chars[i + 1])) {
        return ' '
      } else {
        return chr
      }
    }).join('')
  }
  const normalizeNbspAtStart = (root, node) => {
    const text = node.data
    const firstPos = CaretPosition(node, 0)
    if (isNbspAt(text, 0) && !needsToBeNbsp(root, firstPos)) {
      node.data = ' ' + text.slice(1)
      return true
    } else {
      return false
    }
  }
  const normalizeNbspInMiddleOfTextNode = (node) => {
    const text = node.data
    const newText = normalizeNbspMiddle(text)
    if (newText !== text) {
      node.data = newText
      return true
    } else {
      return false
    }
  }
  const normalizeNbspAtEnd = (root, node) => {
    const text = node.data
    const lastPos = CaretPosition(node, text.length - 1)
    if (isNbspAt(text, text.length - 1) && !needsToBeNbsp(root, lastPos)) {
      node.data = text.slice(0, -1) + ' '
      return true
    } else {
      return false
    }
  }
  const normalizeNbsps = (root, pos) =>
    Optional.some(pos)
      .filter(hasNbsp)
      .bind((pos) => {
        const container = pos.container()
        const normalized =
          normalizeNbspAtStart(root, container) ||
          normalizeNbspInMiddleOfTextNode(container) ||
          normalizeNbspAtEnd(root, container)
        return normalized ? Optional.some(pos) : Optional.none()
      })
  const normalizeNbspsInEditor = (editor) => {
    const root = SugarElement.fromDom(editor.getBody())
    if (editor.selection.isCollapsed()) {
      normalizeNbsps(root, CaretPosition.fromRangeStart(editor.selection.getRng())).each((pos) => {
        editor.selection.setRng(pos.toRange())
      })
    }
  }

  const normalize$1 = (node, offset, count) => {
    if (count === 0) {
      return
    }
    const elm = SugarElement.fromDom(node)
    const root = ancestor$3(elm, isBlock$2).getOr(elm)
    const whitespace = node.data.slice(offset, offset + count)
    const isEndOfContent =
      offset + count >= node.data.length && needsToBeNbspRight(root, CaretPosition(node, node.data.length))
    const isStartOfContent = offset === 0 && needsToBeNbspLeft(root, CaretPosition(node, 0))
    node.replaceData(offset, count, normalize$4(whitespace, 4, isStartOfContent, isEndOfContent))
  }
  const normalizeWhitespaceAfter = (node, offset) => {
    const content = node.data.slice(offset)
    const whitespaceCount = content.length - lTrim(content).length
    normalize$1(node, offset, whitespaceCount)
  }
  const normalizeWhitespaceBefore = (node, offset) => {
    const content = node.data.slice(0, offset)
    const whitespaceCount = content.length - rTrim(content).length
    normalize$1(node, offset - whitespaceCount, whitespaceCount)
  }
  const mergeTextNodes = (prevNode, nextNode, normalizeWhitespace, mergeToPrev = true) => {
    const whitespaceOffset = rTrim(prevNode.data).length
    const newNode = mergeToPrev ? prevNode : nextNode
    const removeNode = mergeToPrev ? nextNode : prevNode
    if (mergeToPrev) {
      newNode.appendData(removeNode.data)
    } else {
      newNode.insertData(0, removeNode.data)
    }
    remove$6(SugarElement.fromDom(removeNode))
    if (normalizeWhitespace) {
      normalizeWhitespaceAfter(newNode, whitespaceOffset)
    }
    return newNode
  }

  const needsReposition = (pos, elm) => {
    const container = pos.container()
    const offset = pos.offset()
    return (
      CaretPosition.isTextPosition(pos) === false &&
      container === elm.parentNode &&
      offset > CaretPosition.before(elm).offset()
    )
  }
  const reposition = (elm, pos) => (needsReposition(pos, elm) ? CaretPosition(pos.container(), pos.offset() - 1) : pos)
  const beforeOrStartOf = (node) => (isText$9(node) ? CaretPosition(node, 0) : CaretPosition.before(node))
  const afterOrEndOf = (node) => (isText$9(node) ? CaretPosition(node, node.data.length) : CaretPosition.after(node))
  const getPreviousSiblingCaretPosition = (elm) => {
    if (isCaretCandidate$3(elm.previousSibling)) {
      return Optional.some(afterOrEndOf(elm.previousSibling))
    } else {
      return elm.previousSibling ? lastPositionIn(elm.previousSibling) : Optional.none()
    }
  }
  const getNextSiblingCaretPosition = (elm) => {
    if (isCaretCandidate$3(elm.nextSibling)) {
      return Optional.some(beforeOrStartOf(elm.nextSibling))
    } else {
      return elm.nextSibling ? firstPositionIn(elm.nextSibling) : Optional.none()
    }
  }
  const findCaretPositionBackwardsFromElm = (rootElement, elm) => {
    const startPosition = CaretPosition.before(elm.previousSibling ? elm.previousSibling : elm.parentNode)
    return prevPosition(rootElement, startPosition).fold(
      () => nextPosition(rootElement, CaretPosition.after(elm)),
      Optional.some
    )
  }
  const findCaretPositionForwardsFromElm = (rootElement, elm) =>
    nextPosition(rootElement, CaretPosition.after(elm)).fold(
      () => prevPosition(rootElement, CaretPosition.before(elm)),
      Optional.some
    )
  const findCaretPositionBackwards = (rootElement, elm) =>
    getPreviousSiblingCaretPosition(elm)
      .orThunk(() => getNextSiblingCaretPosition(elm))
      .orThunk(() => findCaretPositionBackwardsFromElm(rootElement, elm))
  const findCaretPositionForward = (rootElement, elm) =>
    getNextSiblingCaretPosition(elm)
      .orThunk(() => getPreviousSiblingCaretPosition(elm))
      .orThunk(() => findCaretPositionForwardsFromElm(rootElement, elm))
  const findCaretPosition = (forward, rootElement, elm) =>
    forward ? findCaretPositionForward(rootElement, elm) : findCaretPositionBackwards(rootElement, elm)
  const findCaretPosOutsideElmAfterDelete = (forward, rootElement, elm) =>
    findCaretPosition(forward, rootElement, elm).map(curry(reposition, elm))
  const setSelection$1 = (editor, forward, pos) => {
    pos.fold(
      () => {
        editor.focus()
      },
      (pos) => {
        editor.selection.setRng(pos.toRange(), forward)
      }
    )
  }
  const eqRawNode = (rawNode) => (elm) => elm.dom === rawNode
  const isBlock = (editor, elm) => elm && has$2(editor.schema.getBlockElements(), name(elm))
  const paddEmptyBlock = (elm) => {
    if (isEmpty$2(elm)) {
      const br = SugarElement.fromHtml('<br data-mce-bogus="1">')
      empty(elm)
      append$1(elm, br)
      return Optional.some(CaretPosition.before(br.dom))
    } else {
      return Optional.none()
    }
  }
  const deleteNormalized = (elm, afterDeletePosOpt, normalizeWhitespace) => {
    const prevTextOpt = prevSibling(elm).filter(isText$a)
    const nextTextOpt = nextSibling(elm).filter(isText$a)
    remove$6(elm)
    return lift3(prevTextOpt, nextTextOpt, afterDeletePosOpt, (prev, next, pos) => {
      const prevNode = prev.dom,
        nextNode = next.dom
      const offset = prevNode.data.length
      mergeTextNodes(prevNode, nextNode, normalizeWhitespace)
      return pos.container() === nextNode ? CaretPosition(prevNode, offset) : pos
    }).orThunk(() => {
      if (normalizeWhitespace) {
        prevTextOpt.each((elm) => normalizeWhitespaceBefore(elm.dom, elm.dom.length))
        nextTextOpt.each((elm) => normalizeWhitespaceAfter(elm.dom, 0))
      }
      return afterDeletePosOpt
    })
  }
  const isInlineElement = (editor, element) => has$2(editor.schema.getTextInlineElements(), name(element))
  const deleteElement$2 = (editor, forward, elm, moveCaret = true) => {
    const afterDeletePos = findCaretPosOutsideElmAfterDelete(forward, editor.getBody(), elm.dom)
    const parentBlock = ancestor$3(elm, curry(isBlock, editor), eqRawNode(editor.getBody()))
    const normalizedAfterDeletePos = deleteNormalized(elm, afterDeletePos, isInlineElement(editor, elm))
    if (editor.dom.isEmpty(editor.getBody())) {
      editor.setContent('')
      editor.selection.setCursorLocation()
    } else {
      parentBlock.bind(paddEmptyBlock).fold(
        () => {
          if (moveCaret) {
            setSelection$1(editor, forward, normalizedAfterDeletePos)
          }
        },
        (paddPos) => {
          if (moveCaret) {
            setSelection$1(editor, forward, Optional.some(paddPos))
          }
        }
      )
    }
  }

  const strongRtl = /[\u0591-\u07FF\uFB1D-\uFDFF\uFE70-\uFEFC]/
  const hasStrongRtl = (text) => strongRtl.test(text)

  const isInlineTarget = (editor, elm) => is$1(SugarElement.fromDom(elm), getInlineBoundarySelector(editor))
  const isRtl = (element) =>
    DOMUtils.DOM.getStyle(element, 'direction', true) === 'rtl' || hasStrongRtl(element.textContent)
  const findInlineParents = (isInlineTarget, rootNode, pos) =>
    filter$6(DOMUtils.DOM.getParents(pos.container(), '*', rootNode), isInlineTarget)
  const findRootInline = (isInlineTarget, rootNode, pos) => {
    const parents = findInlineParents(isInlineTarget, rootNode, pos)
    return Optional.from(parents[parents.length - 1])
  }
  const hasSameParentBlock = (rootNode, node1, node2) => {
    const block1 = getParentBlock$3(node1, rootNode)
    const block2 = getParentBlock$3(node2, rootNode)
    return block1 && block1 === block2
  }
  const isAtZwsp = (pos) => isBeforeInline(pos) || isAfterInline(pos)
  const normalizePosition = (forward, pos) => {
    if (!pos) {
      return pos
    }
    const container = pos.container(),
      offset = pos.offset()
    if (forward) {
      if (isCaretContainerInline(container)) {
        if (isText$9(container.nextSibling)) {
          return CaretPosition(container.nextSibling, 0)
        } else {
          return CaretPosition.after(container)
        }
      } else {
        return isBeforeInline(pos) ? CaretPosition(container, offset + 1) : pos
      }
    } else {
      if (isCaretContainerInline(container)) {
        if (isText$9(container.previousSibling)) {
          return CaretPosition(container.previousSibling, container.previousSibling.data.length)
        } else {
          return CaretPosition.before(container)
        }
      } else {
        return isAfterInline(pos) ? CaretPosition(container, offset - 1) : pos
      }
    }
  }
  const normalizeForwards = curry(normalizePosition, true)
  const normalizeBackwards = curry(normalizePosition, false)

  const execCommandIgnoreInputEvents = (editor, command) => {
    const inputBlocker = (e) => e.stopImmediatePropagation()
    editor.on('beforeinput input', inputBlocker, true)
    editor.getDoc().execCommand(command)
    editor.off('beforeinput input', inputBlocker)
  }
  const execDeleteCommand = (editor) => execCommandIgnoreInputEvents(editor, 'Delete')
  const execForwardDeleteCommand = (editor) => execCommandIgnoreInputEvents(editor, 'ForwardDelete')
  const isBeforeRoot = (rootNode) => (elm) => eq(rootNode, SugarElement.fromDom(elm.dom.parentNode))
  const isTextBlockOrListItem = (element) => isTextBlock$2(element) || isListItem(element)
  const getParentBlock$2 = (rootNode, elm) => {
    if (contains(rootNode, elm)) {
      return closest$4(elm, isTextBlockOrListItem, isBeforeRoot(rootNode))
    } else {
      return Optional.none()
    }
  }
  const paddEmptyBody = (editor, moveSelection = true) => {
    if (editor.dom.isEmpty(editor.getBody())) {
      editor.setContent('', { no_selection: !moveSelection })
    }
  }
  const willDeleteLastPositionInElement = (forward, fromPos, elm) =>
    lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
      const normalizedFirstPos = normalizePosition(true, firstPos)
      const normalizedLastPos = normalizePosition(false, lastPos)
      const normalizedFromPos = normalizePosition(false, fromPos)
      if (forward) {
        return nextPosition(elm, normalizedFromPos).exists(
          (nextPos) => nextPos.isEqual(normalizedLastPos) && fromPos.isEqual(normalizedFirstPos)
        )
      } else {
        return prevPosition(elm, normalizedFromPos).exists(
          (prevPos) => prevPos.isEqual(normalizedFirstPos) && fromPos.isEqual(normalizedLastPos)
        )
      }
    }).getOr(true)
  const freefallRtl = (root) => {
    const child = isComment$1(root) ? prevSibling(root) : lastChild(root)
    return child.bind(freefallRtl).orThunk(() => Optional.some(root))
  }
  const deleteRangeContents = (editor, rng, root, moveSelection = true) => {
    var _a
    rng.deleteContents()
    const lastNode = freefallRtl(root).getOr(root)
    const lastBlock = SugarElement.fromDom(
      (_a = editor.dom.getParent(lastNode.dom, editor.dom.isBlock)) !== null && _a !== void 0 ? _a : root.dom
    )
    if (lastBlock.dom === editor.getBody()) {
      paddEmptyBody(editor, moveSelection)
    } else if (isEmpty$2(lastBlock)) {
      fillWithPaddingBr(lastBlock)
      if (moveSelection) {
        editor.selection.setCursorLocation(lastBlock.dom, 0)
      }
    }
    if (!eq(root, lastBlock)) {
      const additionalCleanupNodes = is$2(parent(lastBlock), root) ? [] : siblings(lastBlock)
      each$f(additionalCleanupNodes.concat(children(root)), (node) => {
        if (!eq(node, lastBlock) && !contains(node, lastBlock) && isEmpty$2(node)) {
          remove$6(node)
        }
      })
    }
  }

  const isRootFromElement = (root) => (cur) => eq(root, cur)
  const getTableCells = (table) => descendants(table, 'td,th')
  const getTableDetailsFromRange = (rng, isRoot) => {
    const getTable = (node) => getClosestTable(SugarElement.fromDom(node), isRoot)
    const startTable = getTable(rng.startContainer)
    const endTable = getTable(rng.endContainer)
    const isStartInTable = startTable.isSome()
    const isEndInTable = endTable.isSome()
    const isSameTable = lift2(startTable, endTable, eq).getOr(false)
    const isMultiTable = !isSameTable && isStartInTable && isEndInTable
    return {
      startTable,
      endTable,
      isStartInTable,
      isEndInTable,
      isSameTable,
      isMultiTable,
    }
  }

  const tableCellRng = (start, end) => ({
    start,
    end,
  })
  const tableSelection = (rng, table, cells) => ({
    rng,
    table,
    cells,
  })
  const deleteAction = Adt.generate([
    {
      singleCellTable: ['rng', 'cell'],
    },
    { fullTable: ['table'] },
    {
      partialTable: ['cells', 'outsideDetails'],
    },
    {
      multiTable: ['startTableCells', 'endTableCells', 'betweenRng'],
    },
  ])
  const getClosestCell$1 = (container, isRoot) => closest$3(SugarElement.fromDom(container), 'td,th', isRoot)
  const isExpandedCellRng = (cellRng) => !eq(cellRng.start, cellRng.end)
  const getTableFromCellRng = (cellRng, isRoot) =>
    getClosestTable(cellRng.start, isRoot).bind((startParentTable) =>
      getClosestTable(cellRng.end, isRoot).bind((endParentTable) =>
        someIf(eq(startParentTable, endParentTable), startParentTable)
      )
    )
  const isSingleCellTable = (cellRng, isRoot) =>
    !isExpandedCellRng(cellRng) &&
    getTableFromCellRng(cellRng, isRoot).exists((table) => {
      const rows = table.dom.rows
      return rows.length === 1 && rows[0].cells.length === 1
    })
  const getCellRng = (rng, isRoot) => {
    const startCell = getClosestCell$1(rng.startContainer, isRoot)
    const endCell = getClosestCell$1(rng.endContainer, isRoot)
    return lift2(startCell, endCell, tableCellRng)
  }
  const getCellRangeFromStartTable = (isRoot) => (startCell) =>
    getClosestTable(startCell, isRoot).bind((table) =>
      last$3(getTableCells(table)).map((endCell) => tableCellRng(startCell, endCell))
    )
  const getCellRangeFromEndTable = (isRoot) => (endCell) =>
    getClosestTable(endCell, isRoot).bind((table) =>
      head(getTableCells(table)).map((startCell) => tableCellRng(startCell, endCell))
    )
  const getTableSelectionFromCellRng = (isRoot) => (cellRng) =>
    getTableFromCellRng(cellRng, isRoot).map((table) => tableSelection(cellRng, table, getTableCells(table)))
  const getTableSelections = (cellRng, selectionDetails, rng, isRoot) => {
    if (rng.collapsed || !cellRng.forall(isExpandedCellRng)) {
      return Optional.none()
    } else if (selectionDetails.isSameTable) {
      const sameTableSelection = cellRng.bind(getTableSelectionFromCellRng(isRoot))
      return Optional.some({
        start: sameTableSelection,
        end: sameTableSelection,
      })
    } else {
      const startCell = getClosestCell$1(rng.startContainer, isRoot)
      const endCell = getClosestCell$1(rng.endContainer, isRoot)
      const startTableSelection = startCell
        .bind(getCellRangeFromStartTable(isRoot))
        .bind(getTableSelectionFromCellRng(isRoot))
      const endTableSelection = endCell
        .bind(getCellRangeFromEndTable(isRoot))
        .bind(getTableSelectionFromCellRng(isRoot))
      return Optional.some({
        start: startTableSelection,
        end: endTableSelection,
      })
    }
  }
  const getCellIndex = (cells, cell) => findIndex$2(cells, (x) => eq(x, cell))
  const getSelectedCells = (tableSelection) =>
    lift2(
      getCellIndex(tableSelection.cells, tableSelection.rng.start),
      getCellIndex(tableSelection.cells, tableSelection.rng.end),
      (startIndex, endIndex) => tableSelection.cells.slice(startIndex, endIndex + 1)
    )
  const isSingleCellTableContentSelected = (optCellRng, rng, isRoot) =>
    optCellRng.exists((cellRng) => isSingleCellTable(cellRng, isRoot) && hasAllContentsSelected(cellRng.start, rng))
  const unselectCells = (rng, selectionDetails) => {
    const { startTable, endTable } = selectionDetails
    const otherContentRng = rng.cloneRange()
    startTable.each((table) => otherContentRng.setStartAfter(table.dom))
    endTable.each((table) => otherContentRng.setEndBefore(table.dom))
    return otherContentRng
  }
  const handleSingleTable = (cellRng, selectionDetails, rng, isRoot) =>
    getTableSelections(cellRng, selectionDetails, rng, isRoot)
      .bind(({ start, end }) => start.or(end))
      .bind((tableSelection) => {
        const { isSameTable } = selectionDetails
        const selectedCells = getSelectedCells(tableSelection).getOr([])
        if (isSameTable && tableSelection.cells.length === selectedCells.length) {
          return Optional.some(deleteAction.fullTable(tableSelection.table))
        } else if (selectedCells.length > 0) {
          if (isSameTable) {
            return Optional.some(deleteAction.partialTable(selectedCells, Optional.none()))
          } else {
            const otherContentRng = unselectCells(rng, selectionDetails)
            return Optional.some(
              deleteAction.partialTable(
                selectedCells,
                Optional.some({
                  ...selectionDetails,
                  rng: otherContentRng,
                })
              )
            )
          }
        } else {
          return Optional.none()
        }
      })
  const handleMultiTable = (cellRng, selectionDetails, rng, isRoot) =>
    getTableSelections(cellRng, selectionDetails, rng, isRoot).bind(({ start, end }) => {
      const startTableSelectedCells = start.bind(getSelectedCells).getOr([])
      const endTableSelectedCells = end.bind(getSelectedCells).getOr([])
      if (startTableSelectedCells.length > 0 && endTableSelectedCells.length > 0) {
        const otherContentRng = unselectCells(rng, selectionDetails)
        return Optional.some(deleteAction.multiTable(startTableSelectedCells, endTableSelectedCells, otherContentRng))
      } else {
        return Optional.none()
      }
    })
  const getActionFromRange = (root, rng) => {
    const isRoot = isRootFromElement(root)
    const optCellRng = getCellRng(rng, isRoot)
    const selectionDetails = getTableDetailsFromRange(rng, isRoot)
    if (isSingleCellTableContentSelected(optCellRng, rng, isRoot)) {
      return optCellRng.map((cellRng) => deleteAction.singleCellTable(rng, cellRng.start))
    } else if (selectionDetails.isMultiTable) {
      return handleMultiTable(optCellRng, selectionDetails, rng, isRoot)
    } else {
      return handleSingleTable(optCellRng, selectionDetails, rng, isRoot)
    }
  }

  const cleanCells = (cells) =>
    each$f(cells, (cell) => {
      remove$b(cell, 'contenteditable')
      fillWithPaddingBr(cell)
    })
  const getOutsideBlock = (editor, container) =>
    Optional.from(editor.dom.getParent(container, editor.dom.isBlock)).map(SugarElement.fromDom)
  const handleEmptyBlock = (editor, startInTable, emptyBlock) => {
    emptyBlock.each((block) => {
      if (startInTable) {
        remove$6(block)
      } else {
        fillWithPaddingBr(block)
        editor.selection.setCursorLocation(block.dom, 0)
      }
    })
  }
  const deleteContentInsideCell = (editor, cell, rng, isFirstCellInSelection) => {
    const insideTableRng = rng.cloneRange()
    if (isFirstCellInSelection) {
      insideTableRng.setStart(rng.startContainer, rng.startOffset)
      insideTableRng.setEndAfter(cell.dom.lastChild)
    } else {
      insideTableRng.setStartBefore(cell.dom.firstChild)
      insideTableRng.setEnd(rng.endContainer, rng.endOffset)
    }
    deleteCellContents(editor, insideTableRng, cell, false).each((action) => action())
  }
  const collapseAndRestoreCellSelection = (editor) => {
    const selectedCells = getCellsFromEditor(editor)
    const selectedNode = SugarElement.fromDom(editor.selection.getNode())
    if (isTableCell$5(selectedNode.dom) && isEmpty$2(selectedNode)) {
      editor.selection.setCursorLocation(selectedNode.dom, 0)
    } else {
      editor.selection.collapse(true)
    }
    if (selectedCells.length > 1 && exists(selectedCells, (cell) => eq(cell, selectedNode))) {
      set$2(selectedNode, 'data-mce-selected', '1')
    }
  }
  const emptySingleTableCells = (editor, cells, outsideDetails) =>
    Optional.some(() => {
      const editorRng = editor.selection.getRng()
      const cellsToClean = outsideDetails
        .bind(({ rng, isStartInTable }) => {
          const outsideBlock = getOutsideBlock(editor, isStartInTable ? rng.endContainer : rng.startContainer)
          rng.deleteContents()
          handleEmptyBlock(editor, isStartInTable, outsideBlock.filter(isEmpty$2))
          const endPointCell = isStartInTable ? cells[0] : cells[cells.length - 1]
          deleteContentInsideCell(editor, endPointCell, editorRng, isStartInTable)
          if (!isEmpty$2(endPointCell)) {
            return Optional.some(isStartInTable ? cells.slice(1) : cells.slice(0, -1))
          } else {
            return Optional.none()
          }
        })
        .getOr(cells)
      cleanCells(cellsToClean)
      collapseAndRestoreCellSelection(editor)
    })
  const emptyMultiTableCells = (editor, startTableCells, endTableCells, betweenRng) =>
    Optional.some(() => {
      const rng = editor.selection.getRng()
      const startCell = startTableCells[0]
      const endCell = endTableCells[endTableCells.length - 1]
      deleteContentInsideCell(editor, startCell, rng, true)
      deleteContentInsideCell(editor, endCell, rng, false)
      const startTableCellsToClean = isEmpty$2(startCell) ? startTableCells : startTableCells.slice(1)
      const endTableCellsToClean = isEmpty$2(endCell) ? endTableCells : endTableCells.slice(0, -1)
      cleanCells(startTableCellsToClean.concat(endTableCellsToClean))
      betweenRng.deleteContents()
      collapseAndRestoreCellSelection(editor)
    })
  const deleteCellContents = (editor, rng, cell, moveSelection = true) =>
    Optional.some(() => {
      deleteRangeContents(editor, rng, cell, moveSelection)
    })
  const deleteTableElement = (editor, table) => Optional.some(() => deleteElement$2(editor, false, table))
  const deleteCellRange = (editor, rootElm, rng) =>
    getActionFromRange(rootElm, rng).bind((action) =>
      action.fold(
        curry(deleteCellContents, editor),
        curry(deleteTableElement, editor),
        curry(emptySingleTableCells, editor),
        curry(emptyMultiTableCells, editor)
      )
    )
  const deleteCaptionRange = (editor, caption) => emptyElement(editor, caption)
  const deleteTableRange = (editor, rootElm, rng, startElm) =>
    getParentCaption(rootElm, startElm).fold(
      () => deleteCellRange(editor, rootElm, rng),
      (caption) => deleteCaptionRange(editor, caption)
    )
  const deleteRange$2 = (editor, startElm, selectedCells) => {
    const rootNode = SugarElement.fromDom(editor.getBody())
    const rng = editor.selection.getRng()
    return selectedCells.length !== 0
      ? emptySingleTableCells(editor, selectedCells, Optional.none())
      : deleteTableRange(editor, rootNode, rng, startElm)
  }
  const getParentCell = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTableCell$4)
  const getParentCaption = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTag('caption'))
  const deleteBetweenCells = (editor, rootElm, forward, fromCell, from) =>
    navigate(forward, editor.getBody(), from).bind((to) =>
      getParentCell(rootElm, SugarElement.fromDom(to.getNode())).bind((toCell) =>
        eq(toCell, fromCell) ? Optional.none() : Optional.some(noop)
      )
    )
  const emptyElement = (editor, elm) =>
    Optional.some(() => {
      fillWithPaddingBr(elm)
      editor.selection.setCursorLocation(elm.dom, 0)
    })
  const isDeleteOfLastCharPos = (fromCaption, forward, from, to) =>
    firstPositionIn(fromCaption.dom)
      .bind((first) =>
        lastPositionIn(fromCaption.dom).map((last) =>
          forward ? from.isEqual(first) && to.isEqual(last) : from.isEqual(last) && to.isEqual(first)
        )
      )
      .getOr(true)
  const emptyCaretCaption = (editor, elm) => emptyElement(editor, elm)
  const validateCaretCaption = (rootElm, fromCaption, to) =>
    getParentCaption(rootElm, SugarElement.fromDom(to.getNode())).fold(
      () => Optional.some(noop),
      (toCaption) => someIf(!eq(toCaption, fromCaption), noop)
    )
  const deleteCaretInsideCaption = (editor, rootElm, forward, fromCaption, from) =>
    navigate(forward, editor.getBody(), from).fold(
      () => Optional.some(noop),
      (to) =>
        isDeleteOfLastCharPos(fromCaption, forward, from, to)
          ? emptyCaretCaption(editor, fromCaption)
          : validateCaretCaption(rootElm, fromCaption, to)
    )
  const deleteCaretCells = (editor, forward, rootElm, startElm) => {
    const from = CaretPosition.fromRangeStart(editor.selection.getRng())
    return getParentCell(rootElm, startElm).bind((fromCell) =>
      isEmpty$2(fromCell)
        ? emptyElement(editor, fromCell)
        : deleteBetweenCells(editor, rootElm, forward, fromCell, from)
    )
  }
  const deleteCaretCaption = (editor, forward, rootElm, fromCaption) => {
    const from = CaretPosition.fromRangeStart(editor.selection.getRng())
    return isEmpty$2(fromCaption)
      ? emptyElement(editor, fromCaption)
      : deleteCaretInsideCaption(editor, rootElm, forward, fromCaption, from)
  }
  const isNearTable = (forward, pos) => (forward ? isBeforeTable(pos) : isAfterTable(pos))
  const isBeforeOrAfterTable = (editor, forward) => {
    const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng())
    return (
      isNearTable(forward, fromPos) ||
      fromPosition(forward, editor.getBody(), fromPos).exists((pos) => isNearTable(forward, pos))
    )
  }
  const deleteCaret$3 = (editor, forward, startElm) => {
    const rootElm = SugarElement.fromDom(editor.getBody())
    return getParentCaption(rootElm, startElm).fold(
      () =>
        deleteCaretCells(editor, forward, rootElm, startElm).orThunk(() =>
          someIf(isBeforeOrAfterTable(editor, forward), noop)
        ),
      (fromCaption) => deleteCaretCaption(editor, forward, rootElm, fromCaption)
    )
  }
  const backspaceDelete$9 = (editor, forward) => {
    const startElm = SugarElement.fromDom(editor.selection.getStart(true))
    const cells = getCellsFromEditor(editor)
    return editor.selection.isCollapsed() && cells.length === 0
      ? deleteCaret$3(editor, forward, startElm)
      : deleteRange$2(editor, startElm, cells)
  }

  const getContentEditableRoot$1 = (root, node) => {
    while (node && node !== root) {
      if (isContentEditableTrue$5(node) || isContentEditableFalse$b(node)) {
        return node
      }
      node = node.parentNode
    }
    return null
  }

  const traverse = (node, fn) => {
    fn(node)
    if (node.firstChild) {
      traverse(node.firstChild, fn)
    }
    if (node.next) {
      traverse(node.next, fn)
    }
  }
  const matchNode$1 = (nodeFilters, attributeFilters, node, matches) => {
    const name = node.name
    for (let ni = 0, nl = nodeFilters.length; ni < nl; ni++) {
      const filter = nodeFilters[ni]
      if (filter.name === name) {
        const match = matches.nodes[name]
        if (match) {
          match.nodes.push(node)
        } else {
          matches.nodes[name] = {
            filter,
            nodes: [node],
          }
        }
      }
    }
    if (node.attributes) {
      for (let ai = 0, al = attributeFilters.length; ai < al; ai++) {
        const filter = attributeFilters[ai]
        const attrName = filter.name
        if (attrName in node.attributes.map) {
          const match = matches.attributes[attrName]
          if (match) {
            match.nodes.push(node)
          } else {
            matches.attributes[attrName] = {
              filter,
              nodes: [node],
            }
          }
        }
      }
    }
  }
  const findMatchingNodes = (nodeFilters, attributeFilters, node) => {
    const matches = {
      nodes: {},
      attributes: {},
    }
    if (node.firstChild) {
      traverse(node.firstChild, (node) => {
        matchNode$1(nodeFilters, attributeFilters, node, matches)
      })
    }
    return matches
  }
  const runFilters = (matches, args) => {
    const run = (matchRecord) => {
      each$e(matchRecord, (match) => {
        const nodes = filter$6(match.nodes, (node) => isNonNullable(node.parent))
        each$f(match.filter.callbacks, (callback) => {
          callback(nodes, match.filter.name, args)
        })
      })
    }
    run(matches.nodes)
    run(matches.attributes)
  }
  const filter$3 = (nodeFilters, attributeFilters, node, args = {}) => {
    const matches = findMatchingNodes(nodeFilters, attributeFilters, node)
    runFilters(matches, args)
  }

  const paddEmptyNode = (settings, args, blockElements, node) => {
    if (args.insert && blockElements[node.name]) {
      node.empty().append(new AstNode('br', 1))
    } else {
      node.empty().append(new AstNode('#text', 3)).value = nbsp
    }
  }
  const isPaddedWithNbsp = (node) => hasOnlyChild(node, '#text') && node.firstChild.value === nbsp
  const hasOnlyChild = (node, name) =>
    node && node.firstChild && node.firstChild === node.lastChild && node.firstChild.name === name
  const isPadded = (schema, node) => {
    const rule = schema.getElementRule(node.name)
    return rule && rule.paddEmpty
  }
  const isEmpty = (schema, nonEmptyElements, whitespaceElements, node) =>
    node.isEmpty(nonEmptyElements, whitespaceElements, (node) => isPadded(schema, node))
  const isLineBreakNode = (node, blockElements) => node && (node.name in blockElements || node.name === 'br')

  const removeOrUnwrapInvalidNode = (node, schema, originalNodeParent = node.parent) => {
    if (schema.getSpecialElements()[node.name]) {
      node.empty().remove()
    } else {
      const children = node.children()
      for (const childNode of children) {
        if (!schema.isValidChild(originalNodeParent.name, childNode.name)) {
          removeOrUnwrapInvalidNode(childNode, schema, originalNodeParent)
        }
      }
      node.unwrap()
    }
  }
  const cleanInvalidNodes = (nodes, schema, onCreate = noop) => {
    const textBlockElements = schema.getTextBlockElements()
    const nonEmptyElements = schema.getNonEmptyElements()
    const whitespaceElements = schema.getWhitespaceElements()
    const nonSplittableElements = Tools.makeMap('tr,td,th,tbody,thead,tfoot,table')
    const fixed = new Set()
    for (let ni = 0; ni < nodes.length; ni++) {
      const node = nodes[ni]
      let parent
      let newParent
      let tempNode
      if (!node.parent || fixed.has(node)) {
        continue
      }
      if (textBlockElements[node.name] && node.parent.name === 'li') {
        let sibling = node.next
        while (sibling) {
          if (textBlockElements[sibling.name]) {
            sibling.name = 'li'
            fixed.add(sibling)
            node.parent.insert(sibling, node.parent)
          } else {
            break
          }
          sibling = sibling.next
        }
        node.unwrap()
        continue
      }
      const parents = [node]
      for (
        parent = node.parent;
        parent && !schema.isValidChild(parent.name, node.name) && !nonSplittableElements[parent.name];
        parent = parent.parent
      ) {
        parents.push(parent)
      }
      if (parent && parents.length > 1) {
        if (schema.isValidChild(parent.name, node.name)) {
          parents.reverse()
          newParent = parents[0].clone()
          onCreate(newParent)
          let currentNode = newParent
          for (let i = 0; i < parents.length - 1; i++) {
            if (schema.isValidChild(currentNode.name, parents[i].name)) {
              tempNode = parents[i].clone()
              onCreate(tempNode)
              currentNode.append(tempNode)
            } else {
              tempNode = currentNode
            }
            for (let childNode = parents[i].firstChild; childNode && childNode !== parents[i + 1]; ) {
              const nextNode = childNode.next
              tempNode.append(childNode)
              childNode = nextNode
            }
            currentNode = tempNode
          }
          if (!isEmpty(schema, nonEmptyElements, whitespaceElements, newParent)) {
            parent.insert(newParent, parents[0], true)
            parent.insert(node, newParent)
          } else {
            parent.insert(node, parents[0], true)
          }
          parent = parents[0]
          if (isEmpty(schema, nonEmptyElements, whitespaceElements, parent) || hasOnlyChild(parent, 'br')) {
            parent.empty().remove()
          }
        } else {
          removeOrUnwrapInvalidNode(node, schema)
        }
      } else if (node.parent) {
        if (node.name === 'li') {
          let sibling = node.prev
          if (sibling && (sibling.name === 'ul' || sibling.name === 'ol')) {
            sibling.append(node)
            continue
          }
          sibling = node.next
          if (sibling && (sibling.name === 'ul' || sibling.name === 'ol')) {
            sibling.insert(node, sibling.firstChild, true)
            continue
          }
          const wrapper = new AstNode('ul', 1)
          onCreate(wrapper)
          node.wrap(wrapper)
          continue
        }
        if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
          const wrapper = new AstNode('div', 1)
          onCreate(wrapper)
          node.wrap(wrapper)
        } else {
          removeOrUnwrapInvalidNode(node, schema)
        }
      }
    }
  }

  const createRange = (sc, so, ec, eo) => {
    const rng = document.createRange()
    rng.setStart(sc, so)
    rng.setEnd(ec, eo)
    return rng
  }
  const normalizeBlockSelectionRange = (rng) => {
    const startPos = CaretPosition.fromRangeStart(rng)
    const endPos = CaretPosition.fromRangeEnd(rng)
    const rootNode = rng.commonAncestorContainer
    return fromPosition(false, rootNode, endPos)
      .map((newEndPos) => {
        if (!isInSameBlock(startPos, endPos, rootNode) && isInSameBlock(startPos, newEndPos, rootNode)) {
          return createRange(startPos.container(), startPos.offset(), newEndPos.container(), newEndPos.offset())
        } else {
          return rng
        }
      })
      .getOr(rng)
  }
  const normalize = (rng) => (rng.collapsed ? rng : normalizeBlockSelectionRange(rng))

  const hasOnlyOneChild$1 = (node) => {
    return node.firstChild && node.firstChild === node.lastChild
  }
  const isPaddingNode = (node) => {
    return node.name === 'br' || node.value === nbsp
  }
  const isPaddedEmptyBlock = (schema, node) => {
    const blockElements = schema.getBlockElements()
    return blockElements[node.name] && hasOnlyOneChild$1(node) && isPaddingNode(node.firstChild)
  }
  const isEmptyFragmentElement = (schema, node) => {
    const nonEmptyElements = schema.getNonEmptyElements()
    return node && (node.isEmpty(nonEmptyElements) || isPaddedEmptyBlock(schema, node))
  }
  const isListFragment = (schema, fragment) => {
    let firstChild = fragment.firstChild
    let lastChild = fragment.lastChild
    if (firstChild && firstChild.name === 'meta') {
      firstChild = firstChild.next
    }
    if (lastChild && lastChild.attr('id') === 'mce_marker') {
      lastChild = lastChild.prev
    }
    if (isEmptyFragmentElement(schema, lastChild)) {
      lastChild = lastChild.prev
    }
    if (!firstChild || firstChild !== lastChild) {
      return false
    }
    return firstChild.name === 'ul' || firstChild.name === 'ol'
  }
  const cleanupDomFragment = (domFragment) => {
    const firstChild = domFragment.firstChild
    const lastChild = domFragment.lastChild
    if (firstChild && firstChild.nodeName === 'META') {
      firstChild.parentNode.removeChild(firstChild)
    }
    if (lastChild && lastChild.id === 'mce_marker') {
      lastChild.parentNode.removeChild(lastChild)
    }
    return domFragment
  }
  const toDomFragment = (dom, serializer, fragment) => {
    const html = serializer.serialize(fragment)
    const domFragment = dom.createFragment(html)
    return cleanupDomFragment(domFragment)
  }
  const listItems = (elm) => {
    return filter$6(elm.childNodes, (child) => {
      return child.nodeName === 'LI'
    })
  }
  const isPadding = (node) => {
    return node.data === nbsp || isBr$6(node)
  }
  const isListItemPadded = (node) => {
    return node && node.firstChild && node.firstChild === node.lastChild && isPadding(node.firstChild)
  }
  const isEmptyOrPadded = (elm) => {
    return !elm.firstChild || isListItemPadded(elm)
  }
  const trimListItems = (elms) => {
    return elms.length > 0 && isEmptyOrPadded(elms[elms.length - 1]) ? elms.slice(0, -1) : elms
  }
  const getParentLi = (dom, node) => {
    const parentBlock = dom.getParent(node, dom.isBlock)
    return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null
  }
  const isParentBlockLi = (dom, node) => {
    return !!getParentLi(dom, node)
  }
  const getSplit = (parentNode, rng) => {
    const beforeRng = rng.cloneRange()
    const afterRng = rng.cloneRange()
    beforeRng.setStartBefore(parentNode)
    afterRng.setEndAfter(parentNode)
    return [beforeRng.cloneContents(), afterRng.cloneContents()]
  }
  const findFirstIn = (node, rootNode) => {
    const caretPos = CaretPosition.before(node)
    const caretWalker = CaretWalker(rootNode)
    const newCaretPos = caretWalker.next(caretPos)
    return newCaretPos ? newCaretPos.toRange() : null
  }
  const findLastOf = (node, rootNode) => {
    const caretPos = CaretPosition.after(node)
    const caretWalker = CaretWalker(rootNode)
    const newCaretPos = caretWalker.prev(caretPos)
    return newCaretPos ? newCaretPos.toRange() : null
  }
  const insertMiddle = (target, elms, rootNode, rng) => {
    const parts = getSplit(target, rng)
    const parentElm = target.parentNode
    parentElm.insertBefore(parts[0], target)
    Tools.each(elms, (li) => {
      parentElm.insertBefore(li, target)
    })
    parentElm.insertBefore(parts[1], target)
    parentElm.removeChild(target)
    return findLastOf(elms[elms.length - 1], rootNode)
  }
  const insertBefore$1 = (target, elms, rootNode) => {
    const parentElm = target.parentNode
    Tools.each(elms, (elm) => {
      parentElm.insertBefore(elm, target)
    })
    return findFirstIn(target, rootNode)
  }
  const insertAfter$1 = (target, elms, rootNode, dom) => {
    dom.insertAfter(elms.reverse(), target)
    return findLastOf(elms[0], rootNode)
  }
  const insertAtCaret$1 = (serializer, dom, rng, fragment) => {
    const domFragment = toDomFragment(dom, serializer, fragment)
    const liTarget = getParentLi(dom, rng.startContainer)
    const liElms = trimListItems(listItems(domFragment.firstChild))
    const BEGINNING = 1,
      END = 2
    const rootNode = dom.getRoot()
    const isAt = (location) => {
      const caretPos = CaretPosition.fromRangeStart(rng)
      const caretWalker = CaretWalker(dom.getRoot())
      const newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos)
      return newPos ? getParentLi(dom, newPos.getNode()) !== liTarget : true
    }
    if (isAt(BEGINNING)) {
      return insertBefore$1(liTarget, liElms, rootNode)
    } else if (isAt(END)) {
      return insertAfter$1(liTarget, liElms, rootNode, dom)
    }
    return insertMiddle(liTarget, liElms, rootNode, rng)
  }

  const mergeableWrappedElements = ['pre']
  const shouldPasteContentOnly = (dom, fragment, parentNode, root) => {
    var _a
    const firstNode = fragment.firstChild
    const lastNode = fragment.lastChild
    const last = lastNode.attr('data-mce-type') === 'bookmark' ? lastNode.prev : lastNode
    const isPastingSingleElement = firstNode === last
    const isWrappedElement = contains$2(mergeableWrappedElements, firstNode.name)
    if (isPastingSingleElement && isWrappedElement) {
      const isContentEditable = firstNode.attr('contenteditable') !== 'false'
      const isPastingInTheSameBlockTag =
        ((_a = dom.getParent(parentNode, dom.isBlock)) === null || _a === void 0
          ? void 0
          : _a.nodeName.toLowerCase()) === firstNode.name
      const isPastingInContentEditable = Optional.from(getContentEditableRoot$1(root, parentNode)).forall(
        isContentEditableTrue$5
      )
      return isContentEditable && isPastingInTheSameBlockTag && isPastingInContentEditable
    } else {
      return false
    }
  }
  const isTableCell$1 = isTableCell$5
  const isTableCellContentSelected = (dom, rng, cell) => {
    if (cell !== null) {
      const endCell = dom.getParent(rng.endContainer, isTableCell$1)
      return cell === endCell && hasAllContentsSelected(SugarElement.fromDom(cell), rng)
    } else {
      return false
    }
  }
  const validInsertion = (editor, value, parentNode) => {
    if (parentNode.getAttribute('data-mce-bogus') === 'all') {
      parentNode.parentNode.insertBefore(editor.dom.createFragment(value), parentNode)
    } else {
      const node = parentNode.firstChild
      const node2 = parentNode.lastChild
      if (!node || (node === node2 && node.nodeName === 'BR')) {
        editor.dom.setHTML(parentNode, value)
      } else {
        editor.selection.setContent(value, { no_events: true })
      }
    }
  }
  const trimBrsFromTableCell = (dom, elm) => {
    Optional.from(dom.getParent(elm, 'td,th')).map(SugarElement.fromDom).each(trimBlockTrailingBr)
  }
  const reduceInlineTextElements = (editor, merge) => {
    const textInlineElements = editor.schema.getTextInlineElements()
    const dom = editor.dom
    if (merge) {
      const root = editor.getBody()
      const elementUtils = ElementUtils(dom)
      Tools.each(dom.select('*[data-mce-fragment]'), (node) => {
        const isInline = isNonNullable(textInlineElements[node.nodeName.toLowerCase()])
        if (isInline && hasInheritableStyles(dom, node)) {
          for (
            let parentNode = node.parentNode;
            isNonNullable(parentNode) && parentNode !== root;
            parentNode = parentNode.parentNode
          ) {
            const styleConflict = hasStyleConflict(dom, node, parentNode)
            if (styleConflict) {
              break
            }
            if (elementUtils.compare(parentNode, node)) {
              dom.remove(node, true)
              break
            }
          }
        }
      })
    }
  }
  const markFragmentElements = (fragment) => {
    let node = fragment
    while ((node = node.walk())) {
      if (node.type === 1) {
        node.attr('data-mce-fragment', '1')
      }
    }
  }
  const unmarkFragmentElements = (elm) => {
    Tools.each(elm.getElementsByTagName('*'), (elm) => {
      elm.removeAttribute('data-mce-fragment')
    })
  }
  const isPartOfFragment = (node) => {
    return !!node.getAttribute('data-mce-fragment')
  }
  const canHaveChildren = (editor, node) => {
    return node && !editor.schema.getVoidElements()[node.nodeName]
  }
  const moveSelectionToMarker = (editor, marker) => {
    let nextRng
    const dom = editor.dom
    const selection = editor.selection
    if (!marker) {
      return
    }
    selection.scrollIntoView(marker)
    const parentEditableElm = getContentEditableRoot$1(editor.getBody(), marker)
    if (dom.getContentEditable(parentEditableElm) === 'false') {
      dom.remove(marker)
      selection.select(parentEditableElm)
      return
    }
    let rng = dom.createRng()
    const node = marker.previousSibling
    if (isText$9(node)) {
      rng.setStart(node, node.nodeValue.length)
      const node2 = marker.nextSibling
      if (isText$9(node2)) {
        node.appendData(node2.data)
        node2.parentNode.removeChild(node2)
      }
    } else {
      rng.setStartBefore(marker)
      rng.setEndBefore(marker)
    }
    const findNextCaretRng = (rng) => {
      let caretPos = CaretPosition.fromRangeStart(rng)
      const caretWalker = CaretWalker(editor.getBody())
      caretPos = caretWalker.next(caretPos)
      if (caretPos) {
        return caretPos.toRange()
      }
    }
    const parentBlock = dom.getParent(marker, dom.isBlock)
    dom.remove(marker)
    if (parentBlock && dom.isEmpty(parentBlock)) {
      empty(SugarElement.fromDom(parentBlock))
      rng.setStart(parentBlock, 0)
      rng.setEnd(parentBlock, 0)
      if (!isTableCell$1(parentBlock) && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
        rng = nextRng
        dom.remove(parentBlock)
      } else {
        dom.add(parentBlock, dom.create('br', { 'data-mce-bogus': '1' }))
      }
    }
    selection.setRng(rng)
  }
  const deleteSelectedContent = (editor) => {
    const dom = editor.dom
    const rng = normalize(editor.selection.getRng())
    editor.selection.setRng(rng)
    const startCell = dom.getParent(rng.startContainer, isTableCell$1)
    if (isTableCellContentSelected(dom, rng, startCell)) {
      deleteCellContents(editor, rng, SugarElement.fromDom(startCell))
    } else {
      editor.getDoc().execCommand('Delete', false, null)
    }
  }
  const insertHtmlAtCaret = (editor, value, details) => {
    var _a
    let parentNode
    let rng, node
    const selection = editor.selection
    const dom = editor.dom
    const parser = editor.parser
    const merge = details.merge
    const serializer = HtmlSerializer({ validate: true }, editor.schema)
    const bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;</span>'
    if (value.indexOf('{$caret}') === -1) {
      value += '{$caret}'
    }
    value = value.replace(/\{\$caret\}/, bookmarkHtml)
    rng = selection.getRng()
    const caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null)
    const body = editor.getBody()
    if (caretElement === body && selection.isCollapsed()) {
      if (dom.isBlock(body.firstChild) && canHaveChildren(editor, body.firstChild) && dom.isEmpty(body.firstChild)) {
        rng = dom.createRng()
        rng.setStart(body.firstChild, 0)
        rng.setEnd(body.firstChild, 0)
        selection.setRng(rng)
      }
    }
    if (!selection.isCollapsed()) {
      deleteSelectedContent(editor)
    }
    parentNode = selection.getNode()
    const parserArgs = {
      context: parentNode.nodeName.toLowerCase(),
      data: details.data,
      insert: true,
    }
    const fragment = parser.parse(value, parserArgs)
    if (details.paste === true && isListFragment(editor.schema, fragment) && isParentBlockLi(dom, parentNode)) {
      rng = insertAtCaret$1(serializer, dom, selection.getRng(), fragment)
      selection.setRng(rng)
      return value
    }
    if (details.paste === true && shouldPasteContentOnly(dom, fragment, parentNode, editor.getBody())) {
      (_a = fragment.firstChild) === null || _a === void 0 ? void 0 : _a.unwrap()
    }
    markFragmentElements(fragment)
    node = fragment.lastChild
    if (node.attr('id') === 'mce_marker') {
      const marker = node
      for (node = node.prev; node; node = node.walk(true)) {
        if (node.type === 3 || !dom.isBlock(node.name)) {
          if (editor.schema.isValidChild(node.parent.name, 'span')) {
            node.parent.insert(marker, node, node.name === 'br')
          }
          break
        }
      }
    }
    editor._selectionOverrides.showBlockCaretContainer(parentNode)
    if (!parserArgs.invalid) {
      value = serializer.serialize(fragment)
      validInsertion(editor, value, parentNode)
    } else {
      editor.selection.setContent(bookmarkHtml)
      parentNode = selection.getNode()
      const rootNode = editor.getBody()
      if (parentNode.nodeType === 9) {
        parentNode = node = rootNode
      } else {
        node = parentNode
      }
      while (node !== rootNode) {
        parentNode = node
        node = node.parentNode
      }
      value = parentNode === rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode)
      const root = parser.parse(value)
      for (let markerNode = root; markerNode; markerNode = markerNode.walk()) {
        if (markerNode.attr('id') === 'mce_marker') {
          markerNode.replace(fragment)
          break
        }
      }
      const toExtract = fragment.children()
      const parent = fragment.parent.name
      fragment.unwrap()
      const invalidChildren = filter$6(toExtract, (node) => !editor.schema.isValidChild(parent, node.name))
      cleanInvalidNodes(invalidChildren, editor.schema)
      filter$3(parser.getNodeFilters(), parser.getAttributeFilters(), root)
      value = serializer.serialize(root)
      if (parentNode === rootNode) {
        dom.setHTML(rootNode, value)
      } else {
        dom.setOuterHTML(parentNode, value)
      }
    }
    reduceInlineTextElements(editor, merge)
    moveSelectionToMarker(editor, dom.get('mce_marker'))
    unmarkFragmentElements(editor.getBody())
    trimBrsFromTableCell(dom, selection.getStart())
    return value
  }

  const isTreeNode = (content) => content instanceof AstNode

  const moveSelection = (editor) => {
    if (hasFocus(editor)) {
      firstPositionIn(editor.getBody()).each((pos) => {
        const node = pos.getNode()
        const caretPos = isTable$3(node) ? firstPositionIn(node).getOr(pos) : pos
        editor.selection.setRng(caretPos.toRange())
      })
    }
  }
  const setEditorHtml = (editor, html, noSelection) => {
    editor.dom.setHTML(editor.getBody(), html)
    if (noSelection !== true) {
      moveSelection(editor)
    }
  }
  const setContentString = (editor, body, content, args) => {
    if (content.length === 0 || /^\s+$/.test(content)) {
      const padd = '<br data-mce-bogus="1">'
      if (body.nodeName === 'TABLE') {
        content = '<tr><td>' + padd + '</td></tr>'
      } else if (/^(UL|OL)$/.test(body.nodeName)) {
        content = '<li>' + padd + '</li>'
      }
      const forcedRootBlockName = getForcedRootBlock(editor)
      if (editor.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
        content = padd
        content = editor.dom.createHTML(forcedRootBlockName, getForcedRootBlockAttrs(editor), content)
      } else if (!content) {
        content = padd
      }
      setEditorHtml(editor, content, args.no_selection)
      return {
        content,
        html: content,
      }
    } else {
      if (args.format !== 'raw') {
        content = HtmlSerializer({ validate: false }, editor.schema).serialize(
          editor.parser.parse(content, {
            isRootContent: true,
            insert: true,
          })
        )
      }
      const trimmedHtml = isWsPreserveElement(SugarElement.fromDom(body)) ? content : Tools.trim(content)
      setEditorHtml(editor, trimmedHtml, args.no_selection)
      return {
        content: trimmedHtml,
        html: trimmedHtml,
      }
    }
  }
  const setContentTree = (editor, body, content, args) => {
    filter$3(editor.parser.getNodeFilters(), editor.parser.getAttributeFilters(), content)
    const html = HtmlSerializer({ validate: false }, editor.schema).serialize(content)
    const trimmedHtml = isWsPreserveElement(SugarElement.fromDom(body)) ? html : Tools.trim(html)
    setEditorHtml(editor, trimmedHtml, args.no_selection)
    return {
      content,
      html: trimmedHtml,
    }
  }
  const setContentInternal = (editor, content, args) => {
    return Optional.from(editor.getBody())
      .map((body) => {
        if (isTreeNode(content)) {
          return setContentTree(editor, body, content, args)
        } else {
          return setContentString(editor, body, content, args)
        }
      })
      .getOr({
        content,
        html: isTreeNode(args.content) ? '' : args.content,
      })
  }

  const sibling = (scope, predicate) => sibling$1(scope, predicate).isSome()

  const ensureIsRoot = (isRoot) => (isFunction(isRoot) ? isRoot : never)
  const ancestor = (scope, transform, isRoot) => {
    let element = scope.dom
    const stop = ensureIsRoot(isRoot)
    while (element.parentNode) {
      element = element.parentNode
      const el = SugarElement.fromDom(element)
      const transformed = transform(el)
      if (transformed.isSome()) {
        return transformed
      } else if (stop(el)) {
        break
      }
    }
    return Optional.none()
  }
  const closest$2 = (scope, transform, isRoot) => {
    const current = transform(scope)
    const stop = ensureIsRoot(isRoot)
    return current.orThunk(() => (stop(scope) ? Optional.none() : ancestor(scope, transform, stop)))
  }

  const isEq$3 = isEq$5
  const matchesUnInheritedFormatSelector = (ed, node, name) => {
    const formatList = ed.formatter.get(name)
    if (formatList) {
      for (let i = 0; i < formatList.length; i++) {
        const format = formatList[i]
        if (isSelectorFormat(format) && format.inherit === false && ed.dom.is(node, format.selector)) {
          return true
        }
      }
    }
    return false
  }
  const matchParents = (editor, node, name, vars, similar) => {
    const root = editor.dom.getRoot()
    if (node === root) {
      return false
    }
    node = editor.dom.getParent(node, (node) => {
      if (matchesUnInheritedFormatSelector(editor, node, name)) {
        return true
      }
      return node.parentNode === root || !!matchNode(editor, node, name, vars, true)
    })
    return !!matchNode(editor, node, name, vars, similar)
  }
  const matchName = (dom, node, format) => {
    if (isInlineFormat(format) && isEq$3(node, format.inline)) {
      return true
    }
    if (isBlockFormat(format) && isEq$3(node, format.block)) {
      return true
    }
    if (isSelectorFormat(format)) {
      return isElement$6(node) && dom.is(node, format.selector)
    }
    return false
  }
  const matchItems = (dom, node, format, itemName, similar, vars) => {
    const items = format[itemName]
    if (isFunction(format.onmatch)) {
      return format.onmatch(node, format, itemName)
    }
    if (items) {
      if (isUndefined(items.length)) {
        for (const key in items) {
          if (has$2(items, key)) {
            const value = itemName === 'attributes' ? dom.getAttrib(node, key) : getStyle(dom, node, key)
            const expectedValue = replaceVars(items[key], vars)
            const isEmptyValue = isNullable(value) || isEmpty$3(value)
            if (isEmptyValue && isNullable(expectedValue)) {
              continue
            }
            if (similar && isEmptyValue && !format.exact) {
              return false
            }
            if ((!similar || format.exact) && !isEq$3(value, normalizeStyleValue(expectedValue, key))) {
              return false
            }
          }
        }
      } else {
        for (let i = 0; i < items.length; i++) {
          if (itemName === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(dom, node, items[i])) {
            return true
          }
        }
      }
    }
    return true
  }
  const matchNode = (ed, node, name, vars, similar) => {
    const formatList = ed.formatter.get(name)
    const dom = ed.dom
    if (formatList && node) {
      for (let i = 0; i < formatList.length; i++) {
        const format = formatList[i]
        if (
          matchName(ed.dom, node, format) &&
          matchItems(dom, node, format, 'attributes', similar, vars) &&
          matchItems(dom, node, format, 'styles', similar, vars)
        ) {
          const classes = format.classes
          if (classes) {
            for (let x = 0; x < classes.length; x++) {
              if (!ed.dom.hasClass(node, replaceVars(classes[x], vars))) {
                return
              }
            }
          }
          return format
        }
      }
    }
  }
  const match$2 = (editor, name, vars, node, similar) => {
    if (node) {
      return matchParents(editor, node, name, vars, similar)
    }
    node = editor.selection.getNode()
    if (matchParents(editor, node, name, vars, similar)) {
      return true
    }
    const startNode = editor.selection.getStart()
    if (startNode !== node) {
      if (matchParents(editor, startNode, name, vars, similar)) {
        return true
      }
    }
    return false
  }
  const matchAll = (editor, names, vars) => {
    const matchedFormatNames = []
    const checkedMap = {}
    const startElement = editor.selection.getStart()
    editor.dom.getParent(
      startElement,
      (node) => {
        for (let i = 0; i < names.length; i++) {
          const name = names[i]
          if (!checkedMap[name] && matchNode(editor, node, name, vars)) {
            checkedMap[name] = true
            matchedFormatNames.push(name)
          }
        }
      },
      editor.dom.getRoot()
    )
    return matchedFormatNames
  }
  const closest$1 = (editor, names) => {
    const isRoot = (elm) => eq(elm, SugarElement.fromDom(editor.getBody()))
    const match = (elm, name) => (matchNode(editor, elm.dom, name) ? Optional.some(name) : Optional.none())
    return Optional.from(editor.selection.getStart(true))
      .bind((rawElm) =>
        closest$2(SugarElement.fromDom(rawElm), (elm) => findMap(names, (name) => match(elm, name)), isRoot)
      )
      .getOrNull()
  }
  const canApply = (editor, name) => {
    const formatList = editor.formatter.get(name)
    const dom = editor.dom
    if (formatList) {
      const startNode = editor.selection.getStart()
      const parents = getParents$2(dom, startNode)
      for (let x = formatList.length - 1; x >= 0; x--) {
        const format = formatList[x]
        if (!isSelectorFormat(format)) {
          return true
        }
        for (let i = parents.length - 1; i >= 0; i--) {
          if (dom.is(parents[i], format.selector)) {
            return true
          }
        }
      }
    }
    return false
  }
  const matchAllOnNode = (editor, node, formatNames) =>
    foldl(
      formatNames,
      (acc, name) => {
        const matchSimilar = isVariableFormatName(editor, name)
        if (editor.formatter.matchNode(node, name, {}, matchSimilar)) {
          return acc.concat([name])
        } else {
          return acc
        }
      },
      []
    )

  const ZWSP = ZWSP$1,
    CARET_ID = '_mce_caret'
  const importNode = (ownerDocument, node) => {
    return ownerDocument.importNode(node, true)
  }
  const getEmptyCaretContainers = (node) => {
    const nodes = []
    while (node) {
      if ((node.nodeType === 3 && node.nodeValue !== ZWSP) || node.childNodes.length > 1) {
        return []
      }
      if (node.nodeType === 1) {
        nodes.push(node)
      }
      node = node.firstChild
    }
    return nodes
  }
  const isCaretContainerEmpty = (node) => {
    return getEmptyCaretContainers(node).length > 0
  }
  const findFirstTextNode = (node) => {
    if (node) {
      const walker = new DomTreeWalker(node, node)
      for (node = walker.current(); node; node = walker.next()) {
        if (isText$9(node)) {
          return node
        }
      }
    }
    return null
  }
  const createCaretContainer = (fill) => {
    const caretContainer = SugarElement.fromTag('span')
    setAll$1(caretContainer, {
      'id': CARET_ID,
      'data-mce-bogus': '1',
      'data-mce-type': 'format-caret',
    })
    if (fill) {
      append$1(caretContainer, SugarElement.fromText(ZWSP))
    }
    return caretContainer
  }
  const trimZwspFromCaretContainer = (caretContainerNode) => {
    const textNode = findFirstTextNode(caretContainerNode)
    if (textNode && textNode.nodeValue.charAt(0) === ZWSP) {
      textNode.deleteData(0, 1)
    }
    return textNode
  }
  const removeCaretContainerNode = (editor, node, moveCaret = true) => {
    const dom = editor.dom,
      selection = editor.selection
    if (isCaretContainerEmpty(node)) {
      deleteElement$2(editor, false, SugarElement.fromDom(node), moveCaret)
    } else {
      const rng = selection.getRng()
      const block = dom.getParent(node, dom.isBlock)
      const startContainer = rng.startContainer
      const startOffset = rng.startOffset
      const endContainer = rng.endContainer
      const endOffset = rng.endOffset
      const textNode = trimZwspFromCaretContainer(node)
      dom.remove(node, true)
      if (startContainer === textNode && startOffset > 0) {
        rng.setStart(textNode, startOffset - 1)
      }
      if (endContainer === textNode && endOffset > 0) {
        rng.setEnd(textNode, endOffset - 1)
      }
      if (block && dom.isEmpty(block)) {
        fillWithPaddingBr(SugarElement.fromDom(block))
      }
      selection.setRng(rng)
    }
  }
  const removeCaretContainer = (editor, node, moveCaret = true) => {
    const dom = editor.dom,
      selection = editor.selection
    if (!node) {
      node = getParentCaretContainer(editor.getBody(), selection.getStart())
      if (!node) {
        while ((node = dom.get(CARET_ID))) {
          removeCaretContainerNode(editor, node, false)
        }
      }
    } else {
      removeCaretContainerNode(editor, node, moveCaret)
    }
  }
  const insertCaretContainerNode = (editor, caretContainer, formatNode) => {
    const dom = editor.dom,
      block = dom.getParent(formatNode, curry(isTextBlock$1, editor))
    if (block && dom.isEmpty(block)) {
      formatNode.parentNode.replaceChild(caretContainer, formatNode)
    } else {
      removeTrailingBr(SugarElement.fromDom(formatNode))
      if (dom.isEmpty(formatNode)) {
        formatNode.parentNode.replaceChild(caretContainer, formatNode)
      } else {
        dom.insertAfter(caretContainer, formatNode)
      }
    }
  }
  const appendNode = (parentNode, node) => {
    parentNode.appendChild(node)
    return node
  }
  const insertFormatNodesIntoCaretContainer = (formatNodes, caretContainer) => {
    const innerMostFormatNode = foldr(
      formatNodes,
      (parentNode, formatNode) => {
        return appendNode(parentNode, formatNode.cloneNode(false))
      },
      caretContainer
    )
    return appendNode(innerMostFormatNode, innerMostFormatNode.ownerDocument.createTextNode(ZWSP))
  }
  const cleanFormatNode = (editor, caretContainer, formatNode, name, vars, similar) => {
    const formatter = editor.formatter
    const dom = editor.dom
    const validFormats = filter$6(
      keys(formatter.get()),
      (formatName) => formatName !== name && !contains$1(formatName, 'removeformat')
    )
    const matchedFormats = matchAllOnNode(editor, formatNode, validFormats)
    const uniqueFormats = filter$6(matchedFormats, (fmtName) => !areSimilarFormats(editor, fmtName, name))
    if (uniqueFormats.length > 0) {
      const clonedFormatNode = formatNode.cloneNode(false)
      dom.add(caretContainer, clonedFormatNode)
      formatter.remove(name, vars, clonedFormatNode, similar)
      dom.remove(clonedFormatNode)
      return Optional.some(clonedFormatNode)
    } else {
      return Optional.none()
    }
  }
  const applyCaretFormat = (editor, name, vars) => {
    let caretContainer, textNode
    const selection = editor.selection
    const selectionRng = selection.getRng()
    let offset = selectionRng.startOffset
    const container = selectionRng.startContainer
    const text = container.nodeValue
    caretContainer = getParentCaretContainer(editor.getBody(), selection.getStart())
    if (caretContainer) {
      textNode = findFirstTextNode(caretContainer)
    }
    const wordcharRegex = /[^\s\u00a0\u00ad\u200b\ufeff]/
    if (
      text &&
      offset > 0 &&
      offset < text.length &&
      wordcharRegex.test(text.charAt(offset)) &&
      wordcharRegex.test(text.charAt(offset - 1))
    ) {
      const bookmark = selection.getBookmark()
      selectionRng.collapse(true)
      let rng = expandRng(editor, selectionRng, editor.formatter.get(name))
      rng = split(rng)
      editor.formatter.apply(name, vars, rng)
      selection.moveToBookmark(bookmark)
    } else {
      if (!caretContainer || textNode.nodeValue !== ZWSP) {
        caretContainer = importNode(editor.getDoc(), createCaretContainer(true).dom)
        textNode = caretContainer.firstChild
        selectionRng.insertNode(caretContainer)
        offset = 1
        editor.formatter.apply(name, vars, caretContainer)
      } else {
        editor.formatter.apply(name, vars, caretContainer)
      }
      selection.setCursorLocation(textNode, offset)
    }
  }
  const removeCaretFormat = (editor, name, vars, similar) => {
    const dom = editor.dom
    const selection = editor.selection
    let hasContentAfter, node, formatNode
    const parents = []
    const rng = selection.getRng()
    const container = rng.startContainer
    const offset = rng.startOffset
    node = container
    if (container.nodeType === 3) {
      if (offset !== container.nodeValue.length) {
        hasContentAfter = true
      }
      node = node.parentNode
    }
    while (node) {
      if (matchNode(editor, node, name, vars, similar)) {
        formatNode = node
        break
      }
      if (node.nextSibling) {
        hasContentAfter = true
      }
      parents.push(node)
      node = node.parentNode
    }
    if (!formatNode) {
      return
    }
    if (hasContentAfter) {
      const bookmark = selection.getBookmark()
      rng.collapse(true)
      let expandedRng = expandRng(editor, rng, editor.formatter.get(name), true)
      expandedRng = split(expandedRng)
      editor.formatter.remove(name, vars, expandedRng, similar)
      selection.moveToBookmark(bookmark)
    } else {
      const caretContainer = getParentCaretContainer(editor.getBody(), formatNode)
      const newCaretContainer = createCaretContainer(false).dom
      insertCaretContainerNode(editor, newCaretContainer, caretContainer !== null ? caretContainer : formatNode)
      const cleanedFormatNode = cleanFormatNode(editor, newCaretContainer, formatNode, name, vars, similar)
      const caretTextNode = insertFormatNodesIntoCaretContainer(
        parents.concat(cleanedFormatNode.toArray()),
        newCaretContainer
      )
      removeCaretContainerNode(editor, caretContainer, false)
      selection.setCursorLocation(caretTextNode, 1)
      if (dom.isEmpty(formatNode)) {
        dom.remove(formatNode)
      }
    }
  }
  const disableCaretContainer = (editor, keyCode) => {
    const selection = editor.selection,
      body = editor.getBody()
    removeCaretContainer(editor, null, false)
    if ((keyCode === 8 || keyCode === 46) && selection.isCollapsed() && selection.getStart().innerHTML === ZWSP) {
      removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()))
    }
    if (keyCode === 37 || keyCode === 39) {
      removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()))
    }
  }
  const setup$u = (editor) => {
    editor.on('mouseup keydown', (e) => {
      disableCaretContainer(editor, e.keyCode)
    })
  }
  const replaceWithCaretFormat = (targetNode, formatNodes) => {
    const caretContainer = createCaretContainer(false)
    const innerMost = insertFormatNodesIntoCaretContainer(formatNodes, caretContainer.dom)
    before$3(SugarElement.fromDom(targetNode), caretContainer)
    remove$6(SugarElement.fromDom(targetNode))
    return CaretPosition(innerMost, 0)
  }
  const isFormatElement = (editor, element) => {
    const inlineElements = editor.schema.getTextInlineElements()
    return has$2(inlineElements, name(element)) && !isCaretNode(element.dom) && !isBogus$2(element.dom)
  }
  const isEmptyCaretFormatElement = (element) => {
    return isCaretNode(element.dom) && isCaretContainerEmpty(element.dom)
  }

  const postProcessHooks = {}
  const filter$2 = filter$4
  const each$9 = each$d
  const addPostProcessHook = (name, hook) => {
    const hooks = postProcessHooks[name]
    if (!hooks) {
      postProcessHooks[name] = []
    }
    postProcessHooks[name].push(hook)
  }
  const postProcess$1 = (name, editor) => {
    each$9(postProcessHooks[name], (hook) => {
      hook(editor)
    })
  }
  addPostProcessHook('pre', (editor) => {
    const rng = editor.selection.getRng()
    let blocks
    const hasPreSibling = (pre) => {
      return isPre(pre.previousSibling) && indexOf(blocks, pre.previousSibling) !== -1
    }
    const joinPre = (pre1, pre2) => {
      const sPre2 = SugarElement.fromDom(pre2)
      const doc = documentOrOwner(sPre2).dom
      remove$6(sPre2)
      append(SugarElement.fromDom(pre1), [
        SugarElement.fromTag('br', doc),
        SugarElement.fromTag('br', doc),
        ...children(sPre2),
      ])
    }
    const isPre = matchNodeNames(['pre'])
    if (!rng.collapsed) {
      blocks = editor.selection.getSelectedBlocks()
      each$9(filter$2(filter$2(blocks, isPre), hasPreSibling), (pre) => {
        joinPre(pre.previousSibling, pre)
      })
    }
  })

  const each$8 = Tools.each
  const isElementNode$1 = (node) =>
    isElement$6(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$2(node)
  const findElementSibling = (node, siblingName) => {
    for (let sibling = node; sibling; sibling = sibling[siblingName]) {
      if (isText$9(sibling) && isNotEmpty(sibling.data)) {
        return node
      }
      if (isElement$6(sibling) && !isBookmarkNode$1(sibling)) {
        return sibling
      }
    }
    return node
  }
  const mergeSiblingsNodes = (dom, prev, next) => {
    const elementUtils = ElementUtils(dom)
    if (prev && next) {
      prev = findElementSibling(prev, 'previousSibling')
      next = findElementSibling(next, 'nextSibling')
      if (elementUtils.compare(prev, next)) {
        for (let sibling = prev.nextSibling; sibling && sibling !== next; ) {
          const tmpSibling = sibling
          sibling = sibling.nextSibling
          prev.appendChild(tmpSibling)
        }
        dom.remove(next)
        Tools.each(Tools.grep(next.childNodes), (node) => {
          prev.appendChild(node)
        })
        return prev
      }
    }
    return next
  }
  const mergeSiblings = (dom, format, vars, node) => {
    if (node && format.merge_siblings !== false) {
      const newNode = mergeSiblingsNodes(dom, getNonWhiteSpaceSibling(node), node)
      mergeSiblingsNodes(dom, newNode, getNonWhiteSpaceSibling(newNode, true))
    }
  }
  const clearChildStyles = (dom, format, node) => {
    if (format.clear_child_styles) {
      const selector = format.links ? '*:not(a)' : '*'
      each$8(dom.select(selector, node), (node) => {
        if (isElementNode$1(node)) {
          each$8(format.styles, (value, name) => {
            dom.setStyle(node, name, '')
          })
        }
      })
    }
  }
  const processChildElements = (node, filter, process) => {
    each$8(node.childNodes, (node) => {
      if (isElementNode$1(node)) {
        if (filter(node)) {
          process(node)
        }
        if (node.hasChildNodes()) {
          processChildElements(node, filter, process)
        }
      }
    })
  }
  const unwrapEmptySpan = (dom, node) => {
    if (node.nodeName === 'SPAN' && dom.getAttribs(node).length === 0) {
      dom.remove(node, true)
    }
  }
  const hasStyle = (dom, name) => (node) => !!(node && getStyle(dom, node, name))
  const applyStyle = (dom, name, value) => (node) => {
    dom.setStyle(node, name, value)
    if (node.getAttribute('style') === '') {
      node.removeAttribute('style')
    }
    unwrapEmptySpan(dom, node)
  }

  const removeResult = Adt.generate([{ keep: [] }, { rename: ['name'] }, { removed: [] }])
  const MCE_ATTR_RE = /^(src|href|style)$/
  const each$7 = Tools.each
  const isEq$2 = isEq$5
  const isTableCellOrRow = (node) => /^(TR|TH|TD)$/.test(node.nodeName)
  const isChildOfInlineParent = (dom, node, parent) =>
    dom.isChildOf(node, parent) && node !== parent && !dom.isBlock(parent)
  const getContainer = (ed, rng, start) => {
    let container = rng[start ? 'startContainer' : 'endContainer']
    let offset = rng[start ? 'startOffset' : 'endOffset']
    if (isElement$6(container)) {
      const lastIdx = container.childNodes.length - 1
      if (!start && offset) {
        offset--
      }
      container = container.childNodes[offset > lastIdx ? lastIdx : offset]
    }
    if (isText$9(container) && start && offset >= container.nodeValue.length) {
      container = new DomTreeWalker(container, ed.getBody()).next() || container
    }
    if (isText$9(container) && !start && offset === 0) {
      container = new DomTreeWalker(container, ed.getBody()).prev() || container
    }
    return container
  }
  const normalizeTableSelection = (node, start) => {
    const prop = start ? 'firstChild' : 'lastChild'
    if (isTableCellOrRow(node) && node[prop]) {
      const childNode = node[prop]
      if (node.nodeName === 'TR') {
        return childNode[prop] || childNode
      } else {
        return childNode
      }
    }
    return node
  }
  const wrap$1 = (dom, node, name, attrs) => {
    const wrapper = dom.create(name, attrs)
    node.parentNode.insertBefore(wrapper, node)
    wrapper.appendChild(node)
    return wrapper
  }
  const wrapWithSiblings = (dom, node, next, name, attrs) => {
    const start = SugarElement.fromDom(node)
    const wrapper = SugarElement.fromDom(dom.create(name, attrs))
    const siblings = next ? nextSiblings(start) : prevSiblings(start)
    append(wrapper, siblings)
    if (next) {
      before$3(start, wrapper)
      prepend(wrapper, start)
    } else {
      after$4(start, wrapper)
      append$1(wrapper, start)
    }
    return wrapper.dom
  }
  const isColorFormatAndAnchor = (node, format) => format.links && node.nodeName === 'A'
  const removeNode = (ed, node, format) => {
    const parentNode = node.parentNode
    let rootBlockElm
    const dom = ed.dom
    const forcedRootBlock = getForcedRootBlock(ed)
    if (isBlockFormat(format)) {
      if (parentNode === dom.getRoot()) {
        if (!format.list_block || !isEq$2(node, format.list_block)) {
          each$f(from(node.childNodes), (node) => {
            if (isValid(ed, forcedRootBlock, node.nodeName.toLowerCase())) {
              if (!rootBlockElm) {
                rootBlockElm = wrap$1(dom, node, forcedRootBlock)
                dom.setAttribs(rootBlockElm, getForcedRootBlockAttrs(ed))
              } else {
                rootBlockElm.appendChild(node)
              }
            } else {
              rootBlockElm = null
            }
          })
        }
      }
    }
    if (isMixedFormat(format) && !isEq$2(format.inline, node)) {
      return
    }
    dom.remove(node, true)
  }
  const removeFormatInternal = (ed, format, vars, node, compareNode) => {
    let stylesModified
    const dom = ed.dom
    if (!matchName(dom, node, format) && !isColorFormatAndAnchor(node, format)) {
      return removeResult.keep()
    }
    const elm = node
    if (isInlineFormat(format) && format.remove === 'all' && isArray$1(format.preserve_attributes)) {
      const attrsToPreserve = filter$6(dom.getAttribs(elm), (attr) =>
        contains$2(format.preserve_attributes, attr.name.toLowerCase())
      )
      dom.removeAllAttribs(elm)
      each$f(attrsToPreserve, (attr) => dom.setAttrib(elm, attr.name, attr.value))
      if (attrsToPreserve.length > 0) {
        return removeResult.rename('span')
      }
    }
    if (format.remove !== 'all') {
      each$7(format.styles, (value, name) => {
        value = normalizeStyleValue(replaceVars(value, vars), name + '')
        if (isNumber(name)) {
          name = value
          compareNode = null
        }
        if (format.remove_similar || !compareNode || isEq$2(getStyle(dom, compareNode, name), value)) {
          dom.setStyle(elm, name, '')
        }
        stylesModified = true
      })
      if (stylesModified && dom.getAttrib(elm, 'style') === '') {
        elm.removeAttribute('style')
        elm.removeAttribute('data-mce-style')
      }
      each$7(format.attributes, (value, name) => {
        let valueOut
        value = replaceVars(value, vars)
        if (isNumber(name)) {
          name = value
          compareNode = null
        }
        if (format.remove_similar || !compareNode || isEq$2(dom.getAttrib(compareNode, name), value)) {
          if (name === 'class') {
            value = dom.getAttrib(elm, name)
            if (value) {
              valueOut = ''
              each$f(value.split(/\s+/), (cls) => {
                if (/mce\-\w+/.test(cls)) {
                  valueOut += (valueOut ? ' ' : '') + cls
                }
              })
              if (valueOut) {
                dom.setAttrib(elm, name, valueOut)
                return
              }
            }
          }
          if (MCE_ATTR_RE.test(name)) {
            elm.removeAttribute('data-mce-' + name)
          }
          if (name === 'style' && matchNodeNames(['li'])(elm) && dom.getStyle(elm, 'list-style-type') === 'none') {
            elm.removeAttribute(name)
            dom.setStyle(elm, 'list-style-type', 'none')
            return
          }
          if (name === 'class') {
            elm.removeAttribute('className')
          }
          elm.removeAttribute(name)
        }
      })
      each$7(format.classes, (value) => {
        value = replaceVars(value, vars)
        if (!compareNode || dom.hasClass(compareNode, value)) {
          dom.removeClass(elm, value)
        }
      })
      const attrs = dom.getAttribs(elm)
      for (let i = 0; i < attrs.length; i++) {
        const attrName = attrs[i].nodeName
        if (attrName.indexOf('_') !== 0 && attrName.indexOf('data-') !== 0) {
          return removeResult.keep()
        }
      }
    }
    if (format.remove !== 'none') {
      removeNode(ed, elm, format)
      return removeResult.removed()
    }
    return removeResult.keep()
  }
  const removeFormat$1 = (ed, format, vars, node, compareNode) =>
    removeFormatInternal(ed, format, vars, node, compareNode).fold(
      never,
      (newName) => {
        ed.dom.rename(node, newName)
        return true
      },
      always
    )
  const findFormatRoot = (editor, container, name, vars, similar) => {
    let formatRoot
    each$f(getParents$2(editor.dom, container.parentNode).reverse(), (parent) => {
      if (!formatRoot && parent.id !== '_start' && parent.id !== '_end') {
        const format = matchNode(editor, parent, name, vars, similar)
        if (format && format.split !== false) {
          formatRoot = parent
        }
      }
    })
    return formatRoot
  }
  const removeFormatFromClone = (editor, format, vars, clone) =>
    removeFormatInternal(editor, format, vars, clone, clone).fold(
      constant(clone),
      (newName) => {
        const fragment = editor.dom.createFragment()
        fragment.appendChild(clone)
        return editor.dom.rename(clone, newName)
      },
      constant(null)
    )
  const wrapAndSplit = (editor, formatList, formatRoot, container, target, split, format, vars) => {
    let clone, lastClone, firstClone
    const dom = editor.dom
    if (formatRoot) {
      const formatRootParent = formatRoot.parentNode
      for (let parent = container.parentNode; parent && parent !== formatRootParent; parent = parent.parentNode) {
        clone = dom.clone(parent, false)
        for (let i = 0; i < formatList.length; i++) {
          clone = removeFormatFromClone(editor, formatList[i], vars, clone)
          if (clone === null) {
            break
          }
        }
        if (clone) {
          if (lastClone) {
            clone.appendChild(lastClone)
          }
          if (!firstClone) {
            firstClone = clone
          }
          lastClone = clone
        }
      }
      if (split && (!format.mixed || !dom.isBlock(formatRoot))) {
        container = dom.split(formatRoot, container)
      }
      if (lastClone) {
        target.parentNode.insertBefore(lastClone, target)
        firstClone.appendChild(target)
        if (isInlineFormat(format)) {
          mergeSiblings(dom, format, vars, lastClone)
        }
      }
    }
    return container
  }
  const remove$2 = (ed, name, vars, node, similar) => {
    const formatList = ed.formatter.get(name)
    const format = formatList[0]
    let contentEditable = true
    const dom = ed.dom
    const selection = ed.selection
    const splitToFormatRoot = (container) => {
      const formatRoot = findFormatRoot(ed, container, name, vars, similar)
      return wrapAndSplit(ed, formatList, formatRoot, container, container, true, format, vars)
    }
    const isRemoveBookmarkNode = (node) =>
      isBookmarkNode$1(node) && isElement$6(node) && (node.id === '_start' || node.id === '_end')
    const removeNodeFormat = (node) => exists(formatList, (fmt) => removeFormat$1(ed, fmt, vars, node, node))
    const process = (node) => {
      let lastContentEditable = true
      let hasContentEditableState = false
      if (isElement$6(node) && dom.getContentEditable(node)) {
        lastContentEditable = contentEditable
        contentEditable = dom.getContentEditable(node) === 'true'
        hasContentEditableState = true
      }
      const children = from(node.childNodes)
      if (contentEditable && !hasContentEditableState) {
        const removed = removeNodeFormat(node)
        const currentNodeMatches = removed || exists(formatList, (f) => matchName(dom, node, f))
        const parentNode = node.parentNode
        if (!currentNodeMatches && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
          removeNodeFormat(parentNode)
        }
      }
      if (format.deep) {
        if (children.length) {
          for (let i = 0; i < children.length; i++) {
            process(children[i])
          }
          if (hasContentEditableState) {
            contentEditable = lastContentEditable
          }
        }
      }
      const textDecorations = ['underline', 'line-through', 'overline']
      each$f(textDecorations, (decoration) => {
        if (
          isElement$6(node) &&
          ed.dom.getStyle(node, 'text-decoration') === decoration &&
          node.parentNode &&
          getTextDecoration(dom, node.parentNode) === decoration
        ) {
          removeFormat$1(
            ed,
            {
              deep: false,
              exact: true,
              inline: 'span',
              styles: { textDecoration: decoration },
            },
            null,
            node
          )
        }
      })
    }
    const unwrap = (start) => {
      const node = dom.get(start ? '_start' : '_end')
      let out = node[start ? 'firstChild' : 'lastChild']
      if (isRemoveBookmarkNode(out)) {
        out = out[start ? 'firstChild' : 'lastChild']
      }
      if (isText$9(out) && out.data.length === 0) {
        out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling
      }
      dom.remove(node, true)
      return out
    }
    const removeRngStyle = (rng) => {
      let startContainer, endContainer
      let expandedRng = expandRng(ed, rng, formatList, rng.collapsed)
      if (format.split) {
        expandedRng = split(expandedRng)
        startContainer = getContainer(ed, expandedRng, true)
        endContainer = getContainer(ed, expandedRng)
        if (startContainer !== endContainer) {
          startContainer = normalizeTableSelection(startContainer, true)
          endContainer = normalizeTableSelection(endContainer, false)
          if (isChildOfInlineParent(dom, startContainer, endContainer)) {
            const marker = Optional.from(startContainer.firstChild).getOr(startContainer)
            splitToFormatRoot(
              wrapWithSiblings(dom, marker, true, 'span', {
                'id': '_start',
                'data-mce-type': 'bookmark',
              })
            )
            unwrap(true)
            return
          }
          if (isChildOfInlineParent(dom, endContainer, startContainer)) {
            const marker = Optional.from(endContainer.lastChild).getOr(endContainer)
            splitToFormatRoot(
              wrapWithSiblings(dom, marker, false, 'span', {
                'id': '_end',
                'data-mce-type': 'bookmark',
              })
            )
            unwrap(false)
            return
          }
          startContainer = wrap$1(dom, startContainer, 'span', {
            'id': '_start',
            'data-mce-type': 'bookmark',
          })
          endContainer = wrap$1(dom, endContainer, 'span', {
            'id': '_end',
            'data-mce-type': 'bookmark',
          })
          const newRng = dom.createRng()
          newRng.setStartAfter(startContainer)
          newRng.setEndBefore(endContainer)
          walk$3(dom, newRng, (nodes) => {
            each$f(nodes, (n) => {
              if (!isBookmarkNode$1(n) && !isBookmarkNode$1(n.parentNode)) {
                splitToFormatRoot(n)
              }
            })
          })
          splitToFormatRoot(startContainer)
          splitToFormatRoot(endContainer)
          startContainer = unwrap(true)
          endContainer = unwrap()
        } else {
          startContainer = endContainer = splitToFormatRoot(startContainer)
        }
        expandedRng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer
        expandedRng.startOffset = dom.nodeIndex(startContainer)
        expandedRng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer
        expandedRng.endOffset = dom.nodeIndex(endContainer) + 1
      }
      walk$3(dom, expandedRng, (nodes) => {
        each$f(nodes, process)
      })
    }
    if (node) {
      if (isNode(node)) {
        const rng = dom.createRng()
        rng.setStartBefore(node)
        rng.setEndAfter(node)
        removeRngStyle(rng)
      } else {
        removeRngStyle(node)
      }
      fireFormatRemove(ed, name, node, vars)
      return
    }
    if (dom.getContentEditable(selection.getNode()) === 'false') {
      node = selection.getNode()
      for (let i = 0; i < formatList.length; i++) {
        if (formatList[i].ceFalseOverride) {
          if (removeFormat$1(ed, formatList[i], vars, node, node)) {
            break
          }
        }
      }
      fireFormatRemove(ed, name, node, vars)
      return
    }
    if (!selection.isCollapsed() || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
      preserve(selection, true, () => {
        runOnRanges(ed, removeRngStyle)
      })
      if (isInlineFormat(format) && match$2(ed, name, vars, selection.getStart())) {
        moveStart(dom, selection, selection.getRng())
      }
      ed.nodeChanged()
    } else {
      removeCaretFormat(ed, name, vars, similar)
    }
    fireFormatRemove(ed, name, node, vars)
  }

  const each$6 = Tools.each
  const mergeTextDecorationsAndColor = (dom, format, vars, node) => {
    const processTextDecorationsAndColor = (n) => {
      if (n.nodeType === 1 && n.parentNode && n.parentNode.nodeType === 1) {
        const textDecoration = getTextDecoration(dom, n.parentNode)
        if (dom.getStyle(n, 'color') && textDecoration) {
          dom.setStyle(n, 'text-decoration', textDecoration)
        } else if (dom.getStyle(n, 'text-decoration') === textDecoration) {
          dom.setStyle(n, 'text-decoration', null)
        }
      }
    }
    if (format.styles && (format.styles.color || format.styles.textDecoration)) {
      Tools.walk(node, processTextDecorationsAndColor, 'childNodes')
      processTextDecorationsAndColor(node)
    }
  }
  const mergeBackgroundColorAndFontSize = (dom, format, vars, node) => {
    if (format.styles && format.styles.backgroundColor) {
      processChildElements(
        node,
        hasStyle(dom, 'fontSize'),
        applyStyle(dom, 'backgroundColor', replaceVars(format.styles.backgroundColor, vars))
      )
    }
  }
  const mergeSubSup = (dom, format, vars, node) => {
    if (isInlineFormat(format) && (format.inline === 'sub' || format.inline === 'sup')) {
      processChildElements(node, hasStyle(dom, 'fontSize'), applyStyle(dom, 'fontSize', ''))
      dom.remove(dom.select(format.inline === 'sup' ? 'sub' : 'sup', node), true)
    }
  }
  const mergeWithChildren = (editor, formatList, vars, node) => {
    each$6(formatList, (format) => {
      if (isInlineFormat(format)) {
        each$6(editor.dom.select(format.inline, node), (child) => {
          if (!isElementNode$1(child)) {
            return
          }
          removeFormat$1(editor, format, vars, child, format.exact ? child : null)
        })
      }
      clearChildStyles(editor.dom, format, node)
    })
  }
  const mergeWithParents = (editor, format, name, vars, node) => {
    if (matchNode(editor, node.parentNode, name, vars)) {
      if (removeFormat$1(editor, format, vars, node)) {
        return
      }
    }
    if (format.merge_with_parents) {
      editor.dom.getParent(node.parentNode, (parent) => {
        if (matchNode(editor, parent, name, vars)) {
          removeFormat$1(editor, format, vars, node)
          return true
        }
      })
    }
  }

  const each$5 = Tools.each
  const isElementNode = (node) => {
    return isElement$6(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$2(node)
  }
  const canFormatBR = (editor, format, node, parentName) => {
    if (canFormatEmptyLines(editor) && isInlineFormat(format)) {
      const validBRParentElements = getTextRootBlockElements(editor.schema)
      const hasCaretNodeSibling = sibling(SugarElement.fromDom(node), (sibling) => isCaretNode(sibling.dom))
      return (
        hasNonNullableKey(validBRParentElements, parentName) &&
        isEmpty$2(SugarElement.fromDom(node.parentNode), false) &&
        !hasCaretNodeSibling
      )
    } else {
      return false
    }
  }
  const applyFormat$1 = (ed, name, vars, node) => {
    const formatList = ed.formatter.get(name)
    const format = formatList[0]
    const isCollapsed = !node && ed.selection.isCollapsed()
    const dom = ed.dom
    const selection = ed.selection
    const setElementFormat = (elm, fmt = format) => {
      if (isFunction(fmt.onformat)) {
        fmt.onformat(elm, fmt, vars, node)
      }
      each$5(fmt.styles, (value, name) => {
        dom.setStyle(elm, name, replaceVars(value, vars))
      })
      if (fmt.styles) {
        const styleVal = dom.getAttrib(elm, 'style')
        if (styleVal) {
          dom.setAttrib(elm, 'data-mce-style', styleVal)
        }
      }
      each$5(fmt.attributes, (value, name) => {
        dom.setAttrib(elm, name, replaceVars(value, vars))
      })
      each$5(fmt.classes, (value) => {
        value = replaceVars(value, vars)
        if (!dom.hasClass(elm, value)) {
          dom.addClass(elm, value)
        }
      })
    }
    const applyNodeStyle = (formatList, node) => {
      let found = false
      each$5(formatList, (format) => {
        if (!isSelectorFormat(format)) {
          return false
        }
        if (isNonNullable(format.collapsed) && format.collapsed !== isCollapsed) {
          return
        }
        if (dom.is(node, format.selector) && !isCaretNode(node)) {
          setElementFormat(node, format)
          found = true
          return false
        }
      })
      return found
    }
    const createWrapElement = (wrapName) => {
      if (isString(wrapName)) {
        const wrapElm = dom.create(wrapName)
        setElementFormat(wrapElm)
        return wrapElm
      } else {
        return null
      }
    }
    const applyRngStyle = (dom, rng, nodeSpecific) => {
      const newWrappers = []
      let contentEditable = true
      const wrapName = format.inline || format.block
      const wrapElm = createWrapElement(wrapName)
      walk$3(dom, rng, (nodes) => {
        let currentWrapElm
        const process = (node) => {
          let hasContentEditableState = false
          let lastContentEditable = contentEditable
          const nodeName = node.nodeName.toLowerCase()
          const parentNode = node.parentNode
          const parentName = parentNode.nodeName.toLowerCase()
          if (isElement$6(node) && dom.getContentEditable(node)) {
            lastContentEditable = contentEditable
            contentEditable = dom.getContentEditable(node) === 'true'
            hasContentEditableState = true
          }
          if (isBr$6(node) && !canFormatBR(ed, format, node, parentName)) {
            currentWrapElm = null
            if (isBlockFormat(format)) {
              dom.remove(node)
            }
            return
          }
          if (isBlockFormat(format) && format.wrapper && matchNode(ed, node, name, vars)) {
            currentWrapElm = null
            return
          }
          if (
            contentEditable &&
            !hasContentEditableState &&
            isBlockFormat(format) &&
            !format.wrapper &&
            isTextBlock$1(ed, nodeName) &&
            isValid(ed, parentName, wrapName)
          ) {
            const elm = dom.rename(node, wrapName)
            setElementFormat(elm)
            newWrappers.push(elm)
            currentWrapElm = null
            return
          }
          if (isSelectorFormat(format)) {
            let found = applyNodeStyle(formatList, node)
            if (!found && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
              found = applyNodeStyle(formatList, parentNode)
            }
            if (!isInlineFormat(format) || found) {
              currentWrapElm = null
              return
            }
          }
          if (
            contentEditable &&
            !hasContentEditableState &&
            isValid(ed, wrapName, nodeName) &&
            isValid(ed, parentName, wrapName) &&
            !(!nodeSpecific && isText$9(node) && isZwsp(node.data)) &&
            !isCaretNode(node) &&
            (!isInlineFormat(format) || !dom.isBlock(node))
          ) {
            if (!currentWrapElm) {
              currentWrapElm = dom.clone(wrapElm, false)
              node.parentNode.insertBefore(currentWrapElm, node)
              newWrappers.push(currentWrapElm)
            }
            currentWrapElm.appendChild(node)
          } else {
            currentWrapElm = null
            each$f(from(node.childNodes), process)
            if (hasContentEditableState) {
              contentEditable = lastContentEditable
            }
            currentWrapElm = null
          }
        }
        each$f(nodes, process)
      })
      if (format.links === true) {
        each$f(newWrappers, (node) => {
          const process = (node) => {
            if (node.nodeName === 'A') {
              setElementFormat(node, format)
            }
            each$f(from(node.childNodes), process)
          }
          process(node)
        })
      }
      each$f(newWrappers, (node) => {
        const getChildCount = (node) => {
          let count = 0
          each$f(node.childNodes, (node) => {
            if (!isEmptyTextNode$1(node) && !isBookmarkNode$1(node)) {
              count++
            }
          })
          return count
        }
        const mergeStyles = (node) => {
          const childElement = find$2(node.childNodes, isElementNode).filter((child) => matchName(dom, child, format))
          return childElement
            .map((child) => {
              const clone = dom.clone(child, false)
              setElementFormat(clone)
              dom.replace(clone, node, true)
              dom.remove(child, true)
              return clone
            })
            .getOr(node)
        }
        const childCount = getChildCount(node)
        if ((newWrappers.length > 1 || !dom.isBlock(node)) && childCount === 0) {
          dom.remove(node, true)
          return
        }
        if (isInlineFormat(format) || (isBlockFormat(format) && format.wrapper)) {
          if (!format.exact && childCount === 1) {
            node = mergeStyles(node)
          }
          mergeWithChildren(ed, formatList, vars, node)
          mergeWithParents(ed, format, name, vars, node)
          mergeBackgroundColorAndFontSize(dom, format, vars, node)
          mergeTextDecorationsAndColor(dom, format, vars, node)
          mergeSubSup(dom, format, vars, node)
          mergeSiblings(dom, format, vars, node)
        }
      })
    }
    if (dom.getContentEditable(selection.getNode()) === 'false') {
      node = selection.getNode()
      for (let i = 0, l = formatList.length; i < l; i++) {
        const formatItem = formatList[i]
        if (formatItem.ceFalseOverride && isSelectorFormat(formatItem) && dom.is(node, formatItem.selector)) {
          setElementFormat(node, formatItem)
          break
        }
      }
      fireFormatApply(ed, name, node, vars)
      return
    }
    if (format) {
      if (node) {
        if (isNode(node)) {
          if (!applyNodeStyle(formatList, node)) {
            const rng = dom.createRng()
            rng.setStartBefore(node)
            rng.setEndAfter(node)
            applyRngStyle(dom, expandRng(ed, rng, formatList), true)
          }
        } else {
          applyRngStyle(dom, node, true)
        }
      } else {
        if (!isCollapsed || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
          selection.setRng(normalize(selection.getRng()))
          preserve(selection, true, () => {
            runOnRanges(ed, (selectionRng, fake) => {
              const expandedRng = fake ? selectionRng : expandRng(ed, selectionRng, formatList)
              applyRngStyle(dom, expandedRng, false)
            })
          })
          moveStart(dom, selection, selection.getRng())
          ed.nodeChanged()
        } else {
          applyCaretFormat(ed, name, vars)
        }
      }
      postProcess$1(name, ed)
    }
    fireFormatApply(ed, name, node, vars)
  }

  const hasVars = (value) => has$2(value, 'vars')
  const setup$t = (registeredFormatListeners, editor) => {
    registeredFormatListeners.set({})
    editor.on('NodeChange', (e) => {
      updateAndFireChangeCallbacks(editor, e.element, registeredFormatListeners.get())
    })
    editor.on('FormatApply FormatRemove', (e) => {
      const element = Optional.from(e.node)
        .map((nodeOrRange) => (isNode(nodeOrRange) ? nodeOrRange : nodeOrRange.startContainer))
        .bind((node) => (isElement$6(node) ? Optional.some(node) : Optional.from(node.parentElement)))
        .getOrThunk(() => fallbackElement(editor))
      updateAndFireChangeCallbacks(editor, element, registeredFormatListeners.get())
    })
  }
  const fallbackElement = (editor) => editor.selection.getStart()
  const matchingNode = (editor, parents, format, similar, vars) => {
    const isMatchingNode = (node) => {
      const matchingFormat = editor.formatter.matchNode(
        node,
        format,
        vars !== null && vars !== void 0 ? vars : {},
        similar
      )
      return !isUndefined(matchingFormat)
    }
    const isUnableToMatch = (node) => {
      if (matchesUnInheritedFormatSelector(editor, node, format)) {
        return true
      } else {
        if (!similar) {
          return isNonNullable(editor.formatter.matchNode(node, format, vars, true))
        } else {
          return false
        }
      }
    }
    return findUntil$1(parents, isMatchingNode, isUnableToMatch)
  }
  const getParents = (editor, elm) => {
    const element = elm !== null && elm !== void 0 ? elm : fallbackElement(editor)
    return filter$6(getParents$2(editor.dom, element), (node) => isElement$6(node) && !isBogus$2(node))
  }
  const updateAndFireChangeCallbacks = (editor, elm, registeredCallbacks) => {
    const parents = getParents(editor, elm)
    each$e(registeredCallbacks, (data, format) => {
      const runIfChanged = (spec) => {
        const match = matchingNode(editor, parents, format, spec.similar, hasVars(spec) ? spec.vars : undefined)
        const isSet = match.isSome()
        if (spec.state.get() !== isSet) {
          spec.state.set(isSet)
          const node = match.getOr(elm)
          if (hasVars(spec)) {
            spec.callback(isSet, {
              node,
              format,
              parents,
            })
          } else {
            each$f(spec.callbacks, (callback) =>
              callback(isSet, {
                node,
                format,
                parents,
              })
            )
          }
        }
      }
      each$f([data.withSimilar, data.withoutSimilar], runIfChanged)
      each$f(data.withVars, runIfChanged)
    })
  }
  const addListeners = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
    const formatChangeItems = registeredFormatListeners.get()
    each$f(formats.split(','), (format) => {
      const group = get$a(formatChangeItems, format).getOrThunk(() => {
        const base = {
          withSimilar: {
            state: Cell(false),
            similar: true,
            callbacks: [],
          },
          withoutSimilar: {
            state: Cell(false),
            similar: false,
            callbacks: [],
          },
          withVars: [],
        }
        formatChangeItems[format] = base
        return base
      })
      const getCurrent = () => {
        const parents = getParents(editor)
        return matchingNode(editor, parents, format, similar, vars).isSome()
      }
      if (isUndefined(vars)) {
        const toAppendTo = similar ? group.withSimilar : group.withoutSimilar
        toAppendTo.callbacks.push(callback)
        if (toAppendTo.callbacks.length === 1) {
          toAppendTo.state.set(getCurrent())
        }
      } else {
        group.withVars.push({
          state: Cell(getCurrent()),
          similar,
          vars,
          callback,
        })
      }
    })
    registeredFormatListeners.set(formatChangeItems)
  }
  const removeListeners = (registeredFormatListeners, formats, callback) => {
    const formatChangeItems = registeredFormatListeners.get()
    each$f(formats.split(','), (format) =>
      get$a(formatChangeItems, format).each((group) => {
        formatChangeItems[format] = {
          withSimilar: {
            ...group.withSimilar,
            callbacks: filter$6(group.withSimilar.callbacks, (cb) => cb !== callback),
          },
          withoutSimilar: {
            ...group.withoutSimilar,
            callbacks: filter$6(group.withoutSimilar.callbacks, (cb) => cb !== callback),
          },
          withVars: filter$6(group.withVars, (item) => item.callback !== callback),
        }
      })
    )
    registeredFormatListeners.set(formatChangeItems)
  }
  const formatChangedInternal = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
    if (registeredFormatListeners.get() === null) {
      setup$t(registeredFormatListeners, editor)
    }
    addListeners(editor, registeredFormatListeners, formats, callback, similar, vars)
    return { unbind: () => removeListeners(registeredFormatListeners, formats, callback) }
  }

  const toggle = (editor, name, vars, node) => {
    const fmt = editor.formatter.get(name)
    if (match$2(editor, name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
      remove$2(editor, name, vars, node)
    } else {
      applyFormat$1(editor, name, vars, node)
    }
  }

  function _typeof(obj) {
    '@babel/helpers - typeof'
    return (
      (_typeof =
        'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
          ? function (obj) {
              return typeof obj
            }
          : function (obj) {
              return obj && 'function' == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype
                ? 'symbol'
                : typeof obj
            }),
      _typeof(obj)
    )
  }
  function _setPrototypeOf(o, p) {
    _setPrototypeOf =
      Object.setPrototypeOf ||
      function _setPrototypeOf(o, p) {
        o.__proto__ = p
        return o
      }
    return _setPrototypeOf(o, p)
  }
  function _isNativeReflectConstruct() {
    if (typeof Reflect === 'undefined' || !Reflect.construct) return false
    if (Reflect.construct.sham) return false
    if (typeof Proxy === 'function') return true
    try {
      Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}))
      return true
    } catch (e) {
      return false
    }
  }
  function _construct(Parent, args, Class) {
    if (_isNativeReflectConstruct()) {
      _construct = Reflect.construct
    } else {
      _construct = function _construct(Parent, args, Class) {
        var a = [null]
        a.push.apply(a, args)
        var Constructor = Function.bind.apply(Parent, a)
        var instance = new Constructor()
        if (Class) _setPrototypeOf(instance, Class.prototype)
        return instance
      }
    }
    return _construct.apply(null, arguments)
  }
  function _toConsumableArray(arr) {
    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread()
  }
  function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray(arr)
  }
  function _iterableToArray(iter) {
    if ((typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null) || iter['@@iterator'] != null)
      return Array.from(iter)
  }
  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return
    if (typeof o === 'string') return _arrayLikeToArray(o, minLen)
    var n = Object.prototype.toString.call(o).slice(8, -1)
    if (n === 'Object' && o.constructor) n = o.constructor.name
    if (n === 'Map' || n === 'Set') return Array.from(o)
    if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen)
  }
  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length
    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]
    return arr2
  }
  function _nonIterableSpread() {
    throw new TypeError(
      'Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'
    )
  }
  var hasOwnProperty = Object.hasOwnProperty,
    setPrototypeOf = Object.setPrototypeOf,
    isFrozen = Object.isFrozen,
    getPrototypeOf = Object.getPrototypeOf,
    getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor
  var freeze = Object.freeze,
    seal = Object.seal,
    create$8 = Object.create
  var _ref = typeof Reflect !== 'undefined' && Reflect,
    apply = _ref.apply,
    construct = _ref.construct
  if (!apply) {
    apply = function apply(fun, thisValue, args) {
      return fun.apply(thisValue, args)
    }
  }
  if (!freeze) {
    freeze = function freeze(x) {
      return x
    }
  }
  if (!seal) {
    seal = function seal(x) {
      return x
    }
  }
  if (!construct) {
    construct = function construct(Func, args) {
      return _construct(Func, _toConsumableArray(args))
    }
  }
  var arrayForEach = unapply(Array.prototype.forEach)
  var arrayPop = unapply(Array.prototype.pop)
  var arrayPush = unapply(Array.prototype.push)
  var stringToLowerCase = unapply(String.prototype.toLowerCase)
  var stringMatch = unapply(String.prototype.match)
  var stringReplace = unapply(String.prototype.replace)
  var stringIndexOf = unapply(String.prototype.indexOf)
  var stringTrim = unapply(String.prototype.trim)
  var regExpTest = unapply(RegExp.prototype.test)
  var typeErrorCreate = unconstruct(TypeError)
  function unapply(func) {
    return function (thisArg) {
      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key]
      }
      return apply(func, thisArg, args)
    }
  }
  function unconstruct(func) {
    return function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2]
      }
      return construct(func, args)
    }
  }
  function addToSet(set, array) {
    if (setPrototypeOf) {
      setPrototypeOf(set, null)
    }
    var l = array.length
    while (l--) {
      var element = array[l]
      if (typeof element === 'string') {
        var lcElement = stringToLowerCase(element)
        if (lcElement !== element) {
          if (!isFrozen(array)) {
            array[l] = lcElement
          }
          element = lcElement
        }
      }
      set[element] = true
    }
    return set
  }
  function clone(object) {
    var newObject = create$8(null)
    var property
    for (property in object) {
      if (apply(hasOwnProperty, object, [property])) {
        newObject[property] = object[property]
      }
    }
    return newObject
  }
  function lookupGetter(object, prop) {
    while (object !== null) {
      var desc = getOwnPropertyDescriptor(object, prop)
      if (desc) {
        if (desc.get) {
          return unapply(desc.get)
        }
        if (typeof desc.value === 'function') {
          return unapply(desc.value)
        }
      }
      object = getPrototypeOf(object)
    }
    function fallbackValue(element) {
      console.warn('fallback value for', element)
      return null
    }
    return fallbackValue
  }
  var html$1 = freeze([
    'a',
    'abbr',
    'acronym',
    'address',
    'area',
    'article',
    'aside',
    'audio',
    'b',
    'bdi',
    'bdo',
    'big',
    'blink',
    'blockquote',
    'body',
    'br',
    'button',
    'canvas',
    'caption',
    'center',
    'cite',
    'code',
    'col',
    'colgroup',
    'content',
    'data',
    'datalist',
    'dd',
    'decorator',
    'del',
    'details',
    'dfn',
    'dialog',
    'dir',
    'div',
    'dl',
    'dt',
    'element',
    'em',
    'fieldset',
    'figcaption',
    'figure',
    'font',
    'footer',
    'form',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'head',
    'header',
    'hgroup',
    'hr',
    'html',
    'i',
    'img',
    'input',
    'ins',
    'kbd',
    'label',
    'legend',
    'li',
    'main',
    'map',
    'mark',
    'marquee',
    'menu',
    'menuitem',
    'meter',
    'nav',
    'nobr',
    'ol',
    'optgroup',
    'option',
    'output',
    'p',
    'picture',
    'pre',
    'progress',
    'q',
    'rp',
    'rt',
    'ruby',
    's',
    'samp',
    'section',
    'select',
    'shadow',
    'small',
    'source',
    'spacer',
    'span',
    'strike',
    'strong',
    'style',
    'sub',
    'summary',
    'sup',
    'table',
    'tbody',
    'td',
    'template',
    'textarea',
    'tfoot',
    'th',
    'thead',
    'time',
    'tr',
    'track',
    'tt',
    'u',
    'ul',
    'var',
    'video',
    'wbr',
  ])
  var svg$1 = freeze([
    'svg',
    'a',
    'altglyph',
    'altglyphdef',
    'altglyphitem',
    'animatecolor',
    'animatemotion',
    'animatetransform',
    'circle',
    'clippath',
    'defs',
    'desc',
    'ellipse',
    'filter',
    'font',
    'g',
    'glyph',
    'glyphref',
    'hkern',
    'image',
    'line',
    'lineargradient',
    'marker',
    'mask',
    'metadata',
    'mpath',
    'path',
    'pattern',
    'polygon',
    'polyline',
    'radialgradient',
    'rect',
    'stop',
    'style',
    'switch',
    'symbol',
    'text',
    'textpath',
    'title',
    'tref',
    'tspan',
    'view',
    'vkern',
  ])
  var svgFilters = freeze([
    'feBlend',
    'feColorMatrix',
    'feComponentTransfer',
    'feComposite',
    'feConvolveMatrix',
    'feDiffuseLighting',
    'feDisplacementMap',
    'feDistantLight',
    'feFlood',
    'feFuncA',
    'feFuncB',
    'feFuncG',
    'feFuncR',
    'feGaussianBlur',
    'feImage',
    'feMerge',
    'feMergeNode',
    'feMorphology',
    'feOffset',
    'fePointLight',
    'feSpecularLighting',
    'feSpotLight',
    'feTile',
    'feTurbulence',
  ])
  var svgDisallowed = freeze([
    'animate',
    'color-profile',
    'cursor',
    'discard',
    'fedropshadow',
    'font-face',
    'font-face-format',
    'font-face-name',
    'font-face-src',
    'font-face-uri',
    'foreignobject',
    'hatch',
    'hatchpath',
    'mesh',
    'meshgradient',
    'meshpatch',
    'meshrow',
    'missing-glyph',
    'script',
    'set',
    'solidcolor',
    'unknown',
    'use',
  ])
  var mathMl$1 = freeze([
    'math',
    'menclose',
    'merror',
    'mfenced',
    'mfrac',
    'mglyph',
    'mi',
    'mlabeledtr',
    'mmultiscripts',
    'mn',
    'mo',
    'mover',
    'mpadded',
    'mphantom',
    'mroot',
    'mrow',
    'ms',
    'mspace',
    'msqrt',
    'mstyle',
    'msub',
    'msup',
    'msubsup',
    'mtable',
    'mtd',
    'mtext',
    'mtr',
    'munder',
    'munderover',
  ])
  var mathMlDisallowed = freeze([
    'maction',
    'maligngroup',
    'malignmark',
    'mlongdiv',
    'mscarries',
    'mscarry',
    'msgroup',
    'mstack',
    'msline',
    'msrow',
    'semantics',
    'annotation',
    'annotation-xml',
    'mprescripts',
    'none',
  ])
  var text = freeze(['#text'])
  var html = freeze([
    'accept',
    'action',
    'align',
    'alt',
    'autocapitalize',
    'autocomplete',
    'autopictureinpicture',
    'autoplay',
    'background',
    'bgcolor',
    'border',
    'capture',
    'cellpadding',
    'cellspacing',
    'checked',
    'cite',
    'class',
    'clear',
    'color',
    'cols',
    'colspan',
    'controls',
    'controlslist',
    'coords',
    'crossorigin',
    'datetime',
    'decoding',
    'default',
    'dir',
    'disabled',
    'disablepictureinpicture',
    'disableremoteplayback',
    'download',
    'draggable',
    'enctype',
    'enterkeyhint',
    'face',
    'for',
    'headers',
    'height',
    'hidden',
    'high',
    'href',
    'hreflang',
    'id',
    'inputmode',
    'integrity',
    'ismap',
    'kind',
    'label',
    'lang',
    'list',
    'loading',
    'loop',
    'low',
    'max',
    'maxlength',
    'media',
    'method',
    'min',
    'minlength',
    'multiple',
    'muted',
    'name',
    'nonce',
    'noshade',
    'novalidate',
    'nowrap',
    'open',
    'optimum',
    'pattern',
    'placeholder',
    'playsinline',
    'poster',
    'preload',
    'pubdate',
    'radiogroup',
    'readonly',
    'rel',
    'required',
    'rev',
    'reversed',
    'role',
    'rows',
    'rowspan',
    'spellcheck',
    'scope',
    'selected',
    'shape',
    'size',
    'sizes',
    'span',
    'srclang',
    'start',
    'src',
    'srcset',
    'step',
    'style',
    'summary',
    'tabindex',
    'title',
    'translate',
    'type',
    'usemap',
    'valign',
    'value',
    'width',
    'xmlns',
    'slot',
  ])
  var svg = freeze([
    'accent-height',
    'accumulate',
    'additive',
    'alignment-baseline',
    'ascent',
    'attributename',
    'attributetype',
    'azimuth',
    'basefrequency',
    'baseline-shift',
    'begin',
    'bias',
    'by',
    'class',
    'clip',
    'clippathunits',
    'clip-path',
    'clip-rule',
    'color',
    'color-interpolation',
    'color-interpolation-filters',
    'color-profile',
    'color-rendering',
    'cx',
    'cy',
    'd',
    'dx',
    'dy',
    'diffuseconstant',
    'direction',
    'display',
    'divisor',
    'dur',
    'edgemode',
    'elevation',
    'end',
    'fill',
    'fill-opacity',
    'fill-rule',
    'filter',
    'filterunits',
    'flood-color',
    'flood-opacity',
    'font-family',
    'font-size',
    'font-size-adjust',
    'font-stretch',
    'font-style',
    'font-variant',
    'font-weight',
    'fx',
    'fy',
    'g1',
    'g2',
    'glyph-name',
    'glyphref',
    'gradientunits',
    'gradienttransform',
    'height',
    'href',
    'id',
    'image-rendering',
    'in',
    'in2',
    'k',
    'k1',
    'k2',
    'k3',
    'k4',
    'kerning',
    'keypoints',
    'keysplines',
    'keytimes',
    'lang',
    'lengthadjust',
    'letter-spacing',
    'kernelmatrix',
    'kernelunitlength',
    'lighting-color',
    'local',
    'marker-end',
    'marker-mid',
    'marker-start',
    'markerheight',
    'markerunits',
    'markerwidth',
    'maskcontentunits',
    'maskunits',
    'max',
    'mask',
    'media',
    'method',
    'mode',
    'min',
    'name',
    'numoctaves',
    'offset',
    'operator',
    'opacity',
    'order',
    'orient',
    'orientation',
    'origin',
    'overflow',
    'paint-order',
    'path',
    'pathlength',
    'patterncontentunits',
    'patterntransform',
    'patternunits',
    'points',
    'preservealpha',
    'preserveaspectratio',
    'primitiveunits',
    'r',
    'rx',
    'ry',
    'radius',
    'refx',
    'refy',
    'repeatcount',
    'repeatdur',
    'restart',
    'result',
    'rotate',
    'scale',
    'seed',
    'shape-rendering',
    'specularconstant',
    'specularexponent',
    'spreadmethod',
    'startoffset',
    'stddeviation',
    'stitchtiles',
    'stop-color',
    'stop-opacity',
    'stroke-dasharray',
    'stroke-dashoffset',
    'stroke-linecap',
    'stroke-linejoin',
    'stroke-miterlimit',
    'stroke-opacity',
    'stroke',
    'stroke-width',
    'style',
    'surfacescale',
    'systemlanguage',
    'tabindex',
    'targetx',
    'targety',
    'transform',
    'transform-origin',
    'text-anchor',
    'text-decoration',
    'text-rendering',
    'textlength',
    'type',
    'u1',
    'u2',
    'unicode',
    'values',
    'viewbox',
    'visibility',
    'version',
    'vert-adv-y',
    'vert-origin-x',
    'vert-origin-y',
    'width',
    'word-spacing',
    'wrap',
    'writing-mode',
    'xchannelselector',
    'ychannelselector',
    'x',
    'x1',
    'x2',
    'xmlns',
    'y',
    'y1',
    'y2',
    'z',
    'zoomandpan',
  ])
  var mathMl = freeze([
    'accent',
    'accentunder',
    'align',
    'bevelled',
    'close',
    'columnsalign',
    'columnlines',
    'columnspan',
    'denomalign',
    'depth',
    'dir',
    'display',
    'displaystyle',
    'encoding',
    'fence',
    'frame',
    'height',
    'href',
    'id',
    'largeop',
    'length',
    'linethickness',
    'lspace',
    'lquote',
    'mathbackground',
    'mathcolor',
    'mathsize',
    'mathvariant',
    'maxsize',
    'minsize',
    'movablelimits',
    'notation',
    'numalign',
    'open',
    'rowalign',
    'rowlines',
    'rowspacing',
    'rowspan',
    'rspace',
    'rquote',
    'scriptlevel',
    'scriptminsize',
    'scriptsizemultiplier',
    'selection',
    'separator',
    'separators',
    'stretchy',
    'subscriptshift',
    'supscriptshift',
    'symmetric',
    'voffset',
    'width',
    'xmlns',
  ])
  var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink'])
  var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm)
  var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm)
  var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/)
  var ARIA_ATTR = seal(/^aria-[\-\w]+$/)
  var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i)
  var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i)
  var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g)
  var DOCTYPE_NAME = seal(/^html$/i)
  var getGlobal = function getGlobal() {
    return typeof window === 'undefined' ? null : window
  }
  var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
    if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
      return null
    }
    var suffix = null
    var ATTR_NAME = 'data-tt-policy-suffix'
    if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
      suffix = document.currentScript.getAttribute(ATTR_NAME)
    }
    var policyName = 'dompurify' + (suffix ? '#' + suffix : '')
    try {
      return trustedTypes.createPolicy(policyName, {
        createHTML: function createHTML(html) {
          return html
        },
      })
    } catch (_) {
      console.warn('TrustedTypes policy ' + policyName + ' could not be created.')
      return null
    }
  }
  function createDOMPurify() {
    var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal()
    var DOMPurify = function DOMPurify(root) {
      return createDOMPurify(root)
    }
    DOMPurify.version = '2.3.8'
    DOMPurify.removed = []
    if (!window || !window.document || window.document.nodeType !== 9) {
      DOMPurify.isSupported = false
      return DOMPurify
    }
    var originalDocument = window.document
    var document = window.document
    var DocumentFragment = window.DocumentFragment,
      HTMLTemplateElement = window.HTMLTemplateElement,
      Node = window.Node,
      Element = window.Element,
      NodeFilter = window.NodeFilter,
      _window$NamedNodeMap = window.NamedNodeMap,
      NamedNodeMap =
        _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
      HTMLFormElement = window.HTMLFormElement,
      DOMParser = window.DOMParser,
      trustedTypes = window.trustedTypes
    var ElementPrototype = Element.prototype
    var cloneNode = lookupGetter(ElementPrototype, 'cloneNode')
    var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling')
    var getChildNodes = lookupGetter(ElementPrototype, 'childNodes')
    var getParentNode = lookupGetter(ElementPrototype, 'parentNode')
    if (typeof HTMLTemplateElement === 'function') {
      var template = document.createElement('template')
      if (template.content && template.content.ownerDocument) {
        document = template.content.ownerDocument
      }
    }
    var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument)
    var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : ''
    var _document = document,
      implementation = _document.implementation,
      createNodeIterator = _document.createNodeIterator,
      createDocumentFragment = _document.createDocumentFragment,
      getElementsByTagName = _document.getElementsByTagName
    var importNode = originalDocument.importNode
    var documentMode = {}
    try {
      documentMode = clone(document).documentMode ? document.documentMode : {}
    } catch (_) {}
    var hooks = {}
    DOMPurify.isSupported =
      typeof getParentNode === 'function' &&
      implementation &&
      typeof implementation.createHTMLDocument !== 'undefined' &&
      documentMode !== 9
    var MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
      ERB_EXPR$1 = ERB_EXPR,
      DATA_ATTR$1 = DATA_ATTR,
      ARIA_ATTR$1 = ARIA_ATTR,
      IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
      ATTR_WHITESPACE$1 = ATTR_WHITESPACE
    var IS_ALLOWED_URI$1 = IS_ALLOWED_URI
    var ALLOWED_TAGS = null
    var DEFAULT_ALLOWED_TAGS = addToSet(
      {},
      [].concat(
        _toConsumableArray(html$1),
        _toConsumableArray(svg$1),
        _toConsumableArray(svgFilters),
        _toConsumableArray(mathMl$1),
        _toConsumableArray(text)
      )
    )
    var ALLOWED_ATTR = null
    var DEFAULT_ALLOWED_ATTR = addToSet(
      {},
      [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml))
    )
    var CUSTOM_ELEMENT_HANDLING = Object.seal(
      Object.create(null, {
        tagNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null,
        },
        attributeNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null,
        },
        allowCustomizedBuiltInElements: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: false,
        },
      })
    )
    var FORBID_TAGS = null
    var FORBID_ATTR = null
    var ALLOW_ARIA_ATTR = true
    var ALLOW_DATA_ATTR = true
    var ALLOW_UNKNOWN_PROTOCOLS = false
    var SAFE_FOR_TEMPLATES = false
    var WHOLE_DOCUMENT = false
    var SET_CONFIG = false
    var FORCE_BODY = false
    var RETURN_DOM = false
    var RETURN_DOM_FRAGMENT = false
    var RETURN_TRUSTED_TYPE = false
    var SANITIZE_DOM = true
    var KEEP_CONTENT = true
    var IN_PLACE = false
    var USE_PROFILES = {}
    var FORBID_CONTENTS = null
    var DEFAULT_FORBID_CONTENTS = addToSet({}, [
      'annotation-xml',
      'audio',
      'colgroup',
      'desc',
      'foreignobject',
      'head',
      'iframe',
      'math',
      'mi',
      'mn',
      'mo',
      'ms',
      'mtext',
      'noembed',
      'noframes',
      'noscript',
      'plaintext',
      'script',
      'style',
      'svg',
      'template',
      'thead',
      'title',
      'video',
      'xmp',
    ])
    var DATA_URI_TAGS = null
    var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track'])
    var URI_SAFE_ATTRIBUTES = null
    var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
      'alt',
      'class',
      'for',
      'id',
      'label',
      'name',
      'pattern',
      'placeholder',
      'role',
      'summary',
      'title',
      'value',
      'style',
      'xmlns',
    ])
    var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'
    var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
    var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'
    var NAMESPACE = HTML_NAMESPACE
    var IS_EMPTY_INPUT = false
    var PARSER_MEDIA_TYPE
    var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']
    var DEFAULT_PARSER_MEDIA_TYPE = 'text/html'
    var transformCaseFunc
    var CONFIG = null
    var formElement = document.createElement('form')
    var isRegexOrFunction = function isRegexOrFunction(testValue) {
      return testValue instanceof RegExp || testValue instanceof Function
    }
    var _parseConfig = function _parseConfig(cfg) {
      if (CONFIG && CONFIG === cfg) {
        return
      }
      if (!cfg || _typeof(cfg) !== 'object') {
        cfg = {}
      }
      cfg = clone(cfg)
      ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS
      ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR
      URI_SAFE_ATTRIBUTES =
        'ADD_URI_SAFE_ATTR' in cfg
          ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR)
          : DEFAULT_URI_SAFE_ATTRIBUTES
      DATA_URI_TAGS =
        'ADD_DATA_URI_TAGS' in cfg
          ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS)
          : DEFAULT_DATA_URI_TAGS
      FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS
      FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}
      FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}
      USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false
      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false
      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false
      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false
      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false
      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false
      RETURN_DOM = cfg.RETURN_DOM || false
      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false
      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false
      FORCE_BODY = cfg.FORCE_BODY || false
      SANITIZE_DOM = cfg.SANITIZE_DOM !== false
      KEEP_CONTENT = cfg.KEEP_CONTENT !== false
      IN_PLACE = cfg.IN_PLACE || false
      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1
      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE
      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck
      }
      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck
      }
      if (
        cfg.CUSTOM_ELEMENT_HANDLING &&
        typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean'
      ) {
        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
          cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements
      }
      PARSER_MEDIA_TYPE =
        SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1
          ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE)
          : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE)
      transformCaseFunc =
        PARSER_MEDIA_TYPE === 'application/xhtml+xml'
          ? function (x) {
              return x
            }
          : stringToLowerCase
      if (SAFE_FOR_TEMPLATES) {
        ALLOW_DATA_ATTR = false
      }
      if (RETURN_DOM_FRAGMENT) {
        RETURN_DOM = true
      }
      if (USE_PROFILES) {
        ALLOWED_TAGS = addToSet({}, _toConsumableArray(text))
        ALLOWED_ATTR = []
        if (USE_PROFILES.html === true) {
          addToSet(ALLOWED_TAGS, html$1)
          addToSet(ALLOWED_ATTR, html)
        }
        if (USE_PROFILES.svg === true) {
          addToSet(ALLOWED_TAGS, svg$1)
          addToSet(ALLOWED_ATTR, svg)
          addToSet(ALLOWED_ATTR, xml)
        }
        if (USE_PROFILES.svgFilters === true) {
          addToSet(ALLOWED_TAGS, svgFilters)
          addToSet(ALLOWED_ATTR, svg)
          addToSet(ALLOWED_ATTR, xml)
        }
        if (USE_PROFILES.mathMl === true) {
          addToSet(ALLOWED_TAGS, mathMl$1)
          addToSet(ALLOWED_ATTR, mathMl)
          addToSet(ALLOWED_ATTR, xml)
        }
      }
      if (cfg.ADD_TAGS) {
        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
          ALLOWED_TAGS = clone(ALLOWED_TAGS)
        }
        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS)
      }
      if (cfg.ADD_ATTR) {
        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
          ALLOWED_ATTR = clone(ALLOWED_ATTR)
        }
        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR)
      }
      if (cfg.ADD_URI_SAFE_ATTR) {
        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR)
      }
      if (cfg.FORBID_CONTENTS) {
        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
          FORBID_CONTENTS = clone(FORBID_CONTENTS)
        }
        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS)
      }
      if (KEEP_CONTENT) {
        ALLOWED_TAGS['#text'] = true
      }
      if (WHOLE_DOCUMENT) {
        addToSet(ALLOWED_TAGS, ['html', 'head', 'body'])
      }
      if (ALLOWED_TAGS.table) {
        addToSet(ALLOWED_TAGS, ['tbody'])
        delete FORBID_TAGS.tbody
      }
      if (freeze) {
        freeze(cfg)
      }
      CONFIG = cfg
    }
    var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext'])
    var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml'])
    var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script'])
    var ALL_SVG_TAGS = addToSet({}, svg$1)
    addToSet(ALL_SVG_TAGS, svgFilters)
    addToSet(ALL_SVG_TAGS, svgDisallowed)
    var ALL_MATHML_TAGS = addToSet({}, mathMl$1)
    addToSet(ALL_MATHML_TAGS, mathMlDisallowed)
    var _checkValidNamespace = function _checkValidNamespace(element) {
      var parent = getParentNode(element)
      if (!parent || !parent.tagName) {
        parent = {
          namespaceURI: HTML_NAMESPACE,
          tagName: 'template',
        }
      }
      var tagName = stringToLowerCase(element.tagName)
      var parentTagName = stringToLowerCase(parent.tagName)
      if (element.namespaceURI === SVG_NAMESPACE) {
        if (parent.namespaceURI === HTML_NAMESPACE) {
          return tagName === 'svg'
        }
        if (parent.namespaceURI === MATHML_NAMESPACE) {
          return (
            tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
          )
        }
        return Boolean(ALL_SVG_TAGS[tagName])
      }
      if (element.namespaceURI === MATHML_NAMESPACE) {
        if (parent.namespaceURI === HTML_NAMESPACE) {
          return tagName === 'math'
        }
        if (parent.namespaceURI === SVG_NAMESPACE) {
          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]
        }
        return Boolean(ALL_MATHML_TAGS[tagName])
      }
      if (element.namespaceURI === HTML_NAMESPACE) {
        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
          return false
        }
        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
          return false
        }
        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])
      }
      return false
    }
    var _forceRemove = function _forceRemove(node) {
      arrayPush(DOMPurify.removed, { element: node })
      try {
        node.parentNode.removeChild(node)
      } catch (_) {
        try {
          node.outerHTML = emptyHTML
        } catch (_) {
          node.remove()
        }
      }
    }
    var _removeAttribute = function _removeAttribute(name, node) {
      try {
        arrayPush(DOMPurify.removed, {
          attribute: node.getAttributeNode(name),
          from: node,
        })
      } catch (_) {
        arrayPush(DOMPurify.removed, {
          attribute: null,
          from: node,
        })
      }
      node.removeAttribute(name)
      if (name === 'is' && !ALLOWED_ATTR[name]) {
        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
          try {
            _forceRemove(node)
          } catch (_) {}
        } else {
          try {
            node.setAttribute(name, '')
          } catch (_) {}
        }
      }
    }
    var _initDocument = function _initDocument(dirty) {
      var doc
      var leadingWhitespace
      if (FORCE_BODY) {
        dirty = '<remove></remove>' + dirty
      } else {
        var matches = stringMatch(dirty, /^[\r\n\t ]+/)
        leadingWhitespace = matches && matches[0]
      }
      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
        dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>'
      }
      var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty
      if (NAMESPACE === HTML_NAMESPACE) {
        try {
          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE)
        } catch (_) {}
      }
      if (!doc || !doc.documentElement) {
        doc = implementation.createDocument(NAMESPACE, 'template', null)
        try {
          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload
        } catch (_) {}
      }
      var body = doc.body || doc.documentElement
      if (dirty && leadingWhitespace) {
        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null)
      }
      if (NAMESPACE === HTML_NAMESPACE) {
        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]
      }
      return WHOLE_DOCUMENT ? doc.documentElement : body
    }
    var _createIterator = function _createIterator(root) {
      return createNodeIterator.call(
        root.ownerDocument || root,
        root,
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT,
        null,
        false
      )
    }
    var _isClobbered = function _isClobbered(elm) {
      return (
        elm instanceof HTMLFormElement &&
        (typeof elm.nodeName !== 'string' ||
          typeof elm.textContent !== 'string' ||
          typeof elm.removeChild !== 'function' ||
          !(elm.attributes instanceof NamedNodeMap) ||
          typeof elm.removeAttribute !== 'function' ||
          typeof elm.setAttribute !== 'function' ||
          typeof elm.namespaceURI !== 'string' ||
          typeof elm.insertBefore !== 'function')
      )
    }
    var _isNode = function _isNode(object) {
      return _typeof(Node) === 'object'
        ? object instanceof Node
        : object &&
            _typeof(object) === 'object' &&
            typeof object.nodeType === 'number' &&
            typeof object.nodeName === 'string'
    }
    var _executeHook = function _executeHook(entryPoint, currentNode, data) {
      if (!hooks[entryPoint]) {
        return
      }
      arrayForEach(hooks[entryPoint], function (hook) {
        hook.call(DOMPurify, currentNode, data, CONFIG)
      })
    }
    var _sanitizeElements = function _sanitizeElements(currentNode) {
      var content
      _executeHook('beforeSanitizeElements', currentNode, null)
      if (_isClobbered(currentNode)) {
        _forceRemove(currentNode)
        return true
      }
      if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) {
        _forceRemove(currentNode)
        return true
      }
      var tagName = transformCaseFunc(currentNode.nodeName)
      _executeHook('uponSanitizeElement', currentNode, {
        tagName: tagName,
        allowedTags: ALLOWED_TAGS,
      })
      if (
        currentNode.hasChildNodes() &&
        !_isNode(currentNode.firstElementChild) &&
        (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) &&
        regExpTest(/<[/\w]/g, currentNode.innerHTML) &&
        regExpTest(/<[/\w]/g, currentNode.textContent)
      ) {
        _forceRemove(currentNode)
        return true
      }
      if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
        _forceRemove(currentNode)
        return true
      }
      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
        if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
          if (
            CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
            regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)
          )
            return false
          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName))
            return false
        }
        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
          var parentNode = getParentNode(currentNode) || currentNode.parentNode
          var childNodes = getChildNodes(currentNode) || currentNode.childNodes
          if (childNodes && parentNode) {
            var childCount = childNodes.length
            for (var i = childCount - 1; i >= 0; --i) {
              parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode))
            }
          }
        }
        _forceRemove(currentNode)
        return true
      }
      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
        _forceRemove(currentNode)
        return true
      }
      if (
        (tagName === 'noscript' || tagName === 'noembed') &&
        regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)
      ) {
        _forceRemove(currentNode)
        return true
      }
      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
        content = currentNode.textContent
        content = stringReplace(content, MUSTACHE_EXPR$1, ' ')
        content = stringReplace(content, ERB_EXPR$1, ' ')
        if (currentNode.textContent !== content) {
          arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() })
          currentNode.textContent = content
        }
      }
      _executeHook('afterSanitizeElements', currentNode, null)
      return false
    }
    var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
        return false
      }
      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName));
      else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName));
      else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
        if (
          (_basicCustomElementTest(lcTag) &&
            ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
              regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag)) ||
              (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
                CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag))) &&
            ((CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp &&
              regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName)) ||
              (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function &&
                CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)))) ||
          (lcName === 'is' &&
            CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements &&
            ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
              regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) ||
              (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
                CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))))
        );
        else {
          return false
        }
      } else if (URI_SAFE_ATTRIBUTES[lcName]);
      else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, '')));
      else if (
        (lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') &&
        lcTag !== 'script' &&
        stringIndexOf(value, 'data:') === 0 &&
        DATA_URI_TAGS[lcTag]
      );
      else if (
        ALLOW_UNKNOWN_PROTOCOLS &&
        !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))
      );
      else if (!value);
      else {
        return false
      }
      return true
    }
    var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
      return tagName.indexOf('-') > 0
    }
    var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
      var attr
      var value
      var lcName
      var l
      _executeHook('beforeSanitizeAttributes', currentNode, null)
      var attributes = currentNode.attributes
      if (!attributes) {
        return
      }
      var hookEvent = {
        attrName: '',
        attrValue: '',
        keepAttr: true,
        allowedAttributes: ALLOWED_ATTR,
      }
      l = attributes.length
      while (l--) {
        attr = attributes[l]
        var _attr = attr,
          name = _attr.name,
          namespaceURI = _attr.namespaceURI
        value = name === 'value' ? attr.value : stringTrim(attr.value)
        lcName = transformCaseFunc(name)
        var initValue = value
        hookEvent.attrName = lcName
        hookEvent.attrValue = value
        hookEvent.keepAttr = true
        hookEvent.forceKeepAttr = undefined
        _executeHook('uponSanitizeAttribute', currentNode, hookEvent)
        value = hookEvent.attrValue
        if (hookEvent.forceKeepAttr) {
          continue
        }
        if (!hookEvent.keepAttr) {
          _removeAttribute(name, currentNode)
          continue
        }
        if (regExpTest(/\/>/i, value)) {
          _removeAttribute(name, currentNode)
          continue
        }
        if (SAFE_FOR_TEMPLATES) {
          value = stringReplace(value, MUSTACHE_EXPR$1, ' ')
          value = stringReplace(value, ERB_EXPR$1, ' ')
        }
        var lcTag = transformCaseFunc(currentNode.nodeName)
        if (!_isValidAttribute(lcTag, lcName, value)) {
          _removeAttribute(name, currentNode)
          continue
        }
        if (value !== initValue) {
          try {
            if (namespaceURI) {
              currentNode.setAttributeNS(namespaceURI, name, value)
            } else {
              currentNode.setAttribute(name, value)
            }
          } catch (_) {
            _removeAttribute(name, currentNode)
          }
        }
      }
      _executeHook('afterSanitizeAttributes', currentNode, null)
    }
    var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
      var shadowNode
      var shadowIterator = _createIterator(fragment)
      _executeHook('beforeSanitizeShadowDOM', fragment, null)
      while ((shadowNode = shadowIterator.nextNode())) {
        _executeHook('uponSanitizeShadowNode', shadowNode, null)
        if (_sanitizeElements(shadowNode)) {
          continue
        }
        if (shadowNode.content instanceof DocumentFragment) {
          _sanitizeShadowDOM(shadowNode.content)
        }
        _sanitizeAttributes(shadowNode)
      }
      _executeHook('afterSanitizeShadowDOM', fragment, null)
    }
    DOMPurify.sanitize = function (dirty, cfg) {
      var body
      var importedNode
      var currentNode
      var oldNode
      var returnNode
      IS_EMPTY_INPUT = !dirty
      if (IS_EMPTY_INPUT) {
        dirty = '<!-->'
      }
      if (typeof dirty !== 'string' && !_isNode(dirty)) {
        if (typeof dirty.toString !== 'function') {
          throw typeErrorCreate('toString is not a function')
        } else {
          dirty = dirty.toString()
          if (typeof dirty !== 'string') {
            throw typeErrorCreate('dirty is not a string, aborting')
          }
        }
      }
      if (!DOMPurify.isSupported) {
        if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
          if (typeof dirty === 'string') {
            return window.toStaticHTML(dirty)
          }
          if (_isNode(dirty)) {
            return window.toStaticHTML(dirty.outerHTML)
          }
        }
        return dirty
      }
      if (!SET_CONFIG) {
        _parseConfig(cfg)
      }
      DOMPurify.removed = []
      if (typeof dirty === 'string') {
        IN_PLACE = false
      }
      if (IN_PLACE) {
        if (dirty.nodeName) {
          var tagName = transformCaseFunc(dirty.nodeName)
          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place')
          }
        }
      } else if (dirty instanceof Node) {
        body = _initDocument('<!---->')
        importedNode = body.ownerDocument.importNode(dirty, true)
        if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
          body = importedNode
        } else if (importedNode.nodeName === 'HTML') {
          body = importedNode
        } else {
          body.appendChild(importedNode)
        }
      } else {
        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty
        }
        body = _initDocument(dirty)
        if (!body) {
          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''
        }
      }
      if (body && FORCE_BODY) {
        _forceRemove(body.firstChild)
      }
      var nodeIterator = _createIterator(IN_PLACE ? dirty : body)
      while ((currentNode = nodeIterator.nextNode())) {
        if (currentNode.nodeType === 3 && currentNode === oldNode) {
          continue
        }
        if (_sanitizeElements(currentNode)) {
          continue
        }
        if (currentNode.content instanceof DocumentFragment) {
          _sanitizeShadowDOM(currentNode.content)
        }
        _sanitizeAttributes(currentNode)
        oldNode = currentNode
      }
      oldNode = null
      if (IN_PLACE) {
        return dirty
      }
      if (RETURN_DOM) {
        if (RETURN_DOM_FRAGMENT) {
          returnNode = createDocumentFragment.call(body.ownerDocument)
          while (body.firstChild) {
            returnNode.appendChild(body.firstChild)
          }
        } else {
          returnNode = body
        }
        if (ALLOWED_ATTR.shadowroot) {
          returnNode = importNode.call(originalDocument, returnNode, true)
        }
        return returnNode
      }
      var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML
      if (
        WHOLE_DOCUMENT &&
        ALLOWED_TAGS['!doctype'] &&
        body.ownerDocument &&
        body.ownerDocument.doctype &&
        body.ownerDocument.doctype.name &&
        regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)
      ) {
        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML
      }
      if (SAFE_FOR_TEMPLATES) {
        serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' ')
        serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' ')
      }
      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML
    }
    DOMPurify.setConfig = function (cfg) {
      _parseConfig(cfg)
      SET_CONFIG = true
    }
    DOMPurify.clearConfig = function () {
      CONFIG = null
      SET_CONFIG = false
    }
    DOMPurify.isValidAttribute = function (tag, attr, value) {
      if (!CONFIG) {
        _parseConfig({})
      }
      var lcTag = transformCaseFunc(tag)
      var lcName = transformCaseFunc(attr)
      return _isValidAttribute(lcTag, lcName, value)
    }
    DOMPurify.addHook = function (entryPoint, hookFunction) {
      if (typeof hookFunction !== 'function') {
        return
      }
      hooks[entryPoint] = hooks[entryPoint] || []
      arrayPush(hooks[entryPoint], hookFunction)
    }
    DOMPurify.removeHook = function (entryPoint) {
      if (hooks[entryPoint]) {
        return arrayPop(hooks[entryPoint])
      }
    }
    DOMPurify.removeHooks = function (entryPoint) {
      if (hooks[entryPoint]) {
        hooks[entryPoint] = []
      }
    }
    DOMPurify.removeAllHooks = function () {
      hooks = {}
    }
    return DOMPurify
  }
  var purify = createDOMPurify()

  const explode$1 = Tools.explode
  const create$7 = () => {
    const filters = {}
    const addFilter = (name, callback) => {
      each$f(explode$1(name), (name) => {
        if (!has$2(filters, name)) {
          filters[name] = {
            name,
            callbacks: [],
          }
        }
        filters[name].callbacks.push(callback)
      })
    }
    const getFilters = () => values(filters)
    const removeFilter = (name, callback) => {
      each$f(explode$1(name), (name) => {
        if (has$2(filters, name)) {
          if (isNonNullable(callback)) {
            const filter = filters[name]
            const newCallbacks = filter$6(filter.callbacks, (c) => c !== callback)
            if (newCallbacks.length > 0) {
              filter.callbacks = newCallbacks
            } else {
              delete filters[name]
            }
          } else {
            delete filters[name]
          }
        }
      })
    }
    return {
      addFilter,
      getFilters,
      removeFilter,
    }
  }

  const removeAttrs = (node, names) => {
    each$f(names, (name) => {
      node.attr(name, null)
    })
  }
  const addFontToSpansFilter = (domParser, styles, fontSizes) => {
    domParser.addNodeFilter('font', (nodes) => {
      each$f(nodes, (node) => {
        const props = styles.parse(node.attr('style'))
        const color = node.attr('color')
        const face = node.attr('face')
        const size = node.attr('size')
        if (color) {
          props.color = color
        }
        if (face) {
          props['font-family'] = face
        }
        if (size) {
          props['font-size'] = fontSizes[parseInt(node.attr('size'), 10) - 1]
        }
        node.name = 'span'
        node.attr('style', styles.serialize(props))
        removeAttrs(node, ['color', 'face', 'size'])
      })
    })
  }
  const addStrikeFilter = (domParser, schema, styles) => {
    domParser.addNodeFilter('strike', (nodes) => {
      const convertToSTag = schema.type !== 'html4'
      each$f(nodes, (node) => {
        if (convertToSTag) {
          node.name = 's'
        } else {
          const props = styles.parse(node.attr('style'))
          props['text-decoration'] = 'line-through'
          node.name = 'span'
          node.attr('style', styles.serialize(props))
        }
      })
    })
  }
  const addFilters = (domParser, settings, schema) => {
    const styles = Styles()
    if (settings.convert_fonts_to_spans) {
      addFontToSpansFilter(domParser, styles, Tools.explode(settings.font_size_legacy_values))
    }
    addStrikeFilter(domParser, schema, styles)
  }
  const register$5 = (domParser, settings, schema) => {
    if (settings.inline_styles) {
      addFilters(domParser, settings, schema)
    }
  }

  const blobUriToBlob = (url) =>
    fetch(url)
      .then((res) => (res.ok ? res.blob() : Promise.reject()))
      .catch(() => Promise.reject(`Cannot convert ${url} to Blob. Resource might not exist or is inaccessible.`))
  const extractBase64Data = (data) => {
    const matches = /([a-z0-9+\/=\s]+)/i.exec(data)
    return matches ? matches[1] : ''
  }
  const parseDataUri = (uri) => {
    const [type, ...rest] = uri.split(',')
    const data = rest.join(',')
    const matches = /data:([^/]+\/[^;]+)(;.+)?/.exec(type)
    if (matches) {
      const base64Encoded = matches[2] === ';base64'
      const extractedData = base64Encoded ? extractBase64Data(data) : decodeURIComponent(data)
      return Optional.some({
        type: matches[1],
        data: extractedData,
        base64Encoded,
      })
    } else {
      return Optional.none()
    }
  }
  const buildBlob = (type, data, base64Encoded = true) => {
    let str = data
    if (base64Encoded) {
      try {
        str = atob(data)
      } catch (e) {
        return Optional.none()
      }
    }
    const arr = new Uint8Array(str.length)
    for (let i = 0; i < arr.length; i++) {
      arr[i] = str.charCodeAt(i)
    }
    return Optional.some(new Blob([arr], { type }))
  }
  const dataUriToBlob = (uri) => {
    return new Promise((resolve, reject) => {
      parseDataUri(uri)
        .bind(({ type, data, base64Encoded }) => buildBlob(type, data, base64Encoded))
        .fold(() => reject('Invalid data URI'), resolve)
    })
  }
  const uriToBlob = (url) => {
    if (startsWith(url, 'blob:')) {
      return blobUriToBlob(url)
    } else if (startsWith(url, 'data:')) {
      return dataUriToBlob(url)
    } else {
      return Promise.reject('Unknown URI format')
    }
  }
  const blobToDataUri = (blob) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.onloadend = () => {
        resolve(reader.result)
      }
      reader.onerror = () => {
        reject(reader.error.message)
      }
      reader.readAsDataURL(blob)
    })
  }

  let count$1 = 0
  const uniqueId$1 = (prefix) => {
    return (prefix || 'blobid') + count$1++
  }
  const processDataUri = (dataUri, base64Only, generateBlobInfo) => {
    return parseDataUri(dataUri).bind(({ data, type, base64Encoded }) => {
      if (base64Only && !base64Encoded) {
        return Optional.none()
      } else {
        const base64 = base64Encoded ? data : btoa(data)
        return generateBlobInfo(base64, type)
      }
    })
  }
  const createBlobInfo$1 = (blobCache, blob, base64) => {
    const blobInfo = blobCache.create(uniqueId$1(), blob, base64)
    blobCache.add(blobInfo)
    return blobInfo
  }
  const dataUriToBlobInfo = (blobCache, dataUri, base64Only = false) => {
    return processDataUri(dataUri, base64Only, (base64, type) =>
      Optional.from(blobCache.getByData(base64, type)).orThunk(() =>
        buildBlob(type, base64).map((blob) => createBlobInfo$1(blobCache, blob, base64))
      )
    )
  }
  const imageToBlobInfo = (blobCache, imageSrc) => {
    const invalidDataUri = () => Promise.reject('Invalid data URI')
    if (startsWith(imageSrc, 'blob:')) {
      const blobInfo = blobCache.getByUri(imageSrc)
      if (isNonNullable(blobInfo)) {
        return Promise.resolve(blobInfo)
      } else {
        return uriToBlob(imageSrc).then((blob) => {
          return blobToDataUri(blob).then((dataUri) => {
            return processDataUri(dataUri, false, (base64) => {
              return Optional.some(createBlobInfo$1(blobCache, blob, base64))
            }).getOrThunk(invalidDataUri)
          })
        })
      }
    } else if (startsWith(imageSrc, 'data:')) {
      return dataUriToBlobInfo(blobCache, imageSrc).fold(invalidDataUri, (blobInfo) => Promise.resolve(blobInfo))
    } else {
      return Promise.reject('Unknown image data format')
    }
  }

  const isBogusImage = (img) => isNonNullable(img.attr('data-mce-bogus'))
  const isInternalImageSource = (img) =>
    img.attr('src') === Env.transparentSrc || isNonNullable(img.attr('data-mce-placeholder'))
  const registerBase64ImageFilter = (parser, settings) => {
    const { blob_cache: blobCache } = settings
    const processImage = (img) => {
      const inputSrc = img.attr('src')
      if (isInternalImageSource(img) || isBogusImage(img)) {
        return
      }
      dataUriToBlobInfo(blobCache, inputSrc, true).each((blobInfo) => {
        img.attr('src', blobInfo.blobUri())
      })
    }
    if (blobCache) {
      parser.addAttributeFilter('src', (nodes) => each$f(nodes, processImage))
    }
  }
  const register$4 = (parser, settings) => {
    const schema = parser.schema
    if (settings.remove_trailing_brs) {
      parser.addNodeFilter('br', (nodes, _, args) => {
        const blockElements = Tools.extend({}, schema.getBlockElements())
        const nonEmptyElements = schema.getNonEmptyElements()
        const whitespaceElements = schema.getWhitespaceElements()
        blockElements.body = 1
        for (let i = 0, l = nodes.length; i < l; i++) {
          let node = nodes[i]
          let parent = node.parent
          if (blockElements[node.parent.name] && node === parent.lastChild) {
            let prev = node.prev
            while (prev) {
              const prevName = prev.name
              if (prevName !== 'span' || prev.attr('data-mce-type') !== 'bookmark') {
                if (prevName === 'br') {
                  node = null
                }
                break
              }
              prev = prev.prev
            }
            if (node) {
              node.remove()
              if (isEmpty(schema, nonEmptyElements, whitespaceElements, parent)) {
                const elementRule = schema.getElementRule(parent.name)
                if (elementRule) {
                  if (elementRule.removeEmpty) {
                    parent.remove()
                  } else if (elementRule.paddEmpty) {
                    paddEmptyNode(settings, args, blockElements, parent)
                  }
                }
              }
            }
          } else {
            let lastParent = node
            while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
              lastParent = parent
              if (blockElements[parent.name]) {
                break
              }
              parent = parent.parent
            }
            if (lastParent === parent) {
              const textNode = new AstNode('#text', 3)
              textNode.value = nbsp
              node.replace(textNode)
            }
          }
        }
      })
    }
    parser.addAttributeFilter('href', (nodes) => {
      let i = nodes.length
      const appendRel = (rel) => {
        const parts = rel.split(' ').filter((p) => p.length > 0)
        return parts.concat(['noopener']).sort().join(' ')
      }
      const addNoOpener = (rel) => {
        const newRel = rel ? Tools.trim(rel) : ''
        if (!/\b(noopener)\b/g.test(newRel)) {
          return appendRel(newRel)
        } else {
          return newRel
        }
      }
      if (!settings.allow_unsafe_link_target) {
        while (i--) {
          const node = nodes[i]
          if (node.name === 'a' && node.attr('target') === '_blank') {
            node.attr('rel', addNoOpener(node.attr('rel')))
          }
        }
      }
    })
    if (!settings.allow_html_in_named_anchor) {
      parser.addAttributeFilter('id,name', (nodes) => {
        let i = nodes.length,
          sibling,
          prevSibling,
          parent,
          node
        while (i--) {
          node = nodes[i]
          if (node.name === 'a' && node.firstChild && !node.attr('href')) {
            parent = node.parent
            sibling = node.lastChild
            do {
              prevSibling = sibling.prev
              parent.insert(sibling, node)
              sibling = prevSibling
            } while (sibling)
          }
        }
      })
    }
    if (settings.fix_list_elements) {
      parser.addNodeFilter('ul,ol', (nodes) => {
        let i = nodes.length,
          node,
          parentNode
        while (i--) {
          node = nodes[i]
          parentNode = node.parent
          if (parentNode.name === 'ul' || parentNode.name === 'ol') {
            if (node.prev && node.prev.name === 'li') {
              node.prev.append(node)
            } else {
              const li = new AstNode('li', 1)
              li.attr('style', 'list-style-type: none')
              node.wrap(li)
            }
          }
        }
      })
    }
    if (settings.validate && schema.getValidClasses()) {
      parser.addAttributeFilter('class', (nodes) => {
        const validClasses = schema.getValidClasses()
        let i = nodes.length
        while (i--) {
          const node = nodes[i]
          const classList = node.attr('class').split(' ')
          let classValue = ''
          for (let ci = 0; ci < classList.length; ci++) {
            const className = classList[ci]
            let valid = false
            let validClassesMap = validClasses['*']
            if (validClassesMap && validClassesMap[className]) {
              valid = true
            }
            validClassesMap = validClasses[node.name]
            if (!valid && validClassesMap && validClassesMap[className]) {
              valid = true
            }
            if (valid) {
              if (classValue) {
                classValue += ' '
              }
              classValue += className
            }
          }
          if (!classValue.length) {
            classValue = null
          }
          node.attr('class', classValue)
        }
      })
    }
    registerBase64ImageFilter(parser, settings)
  }

  const each$4 = Tools.each,
    trim = Tools.trim
  const queryParts =
    'source protocol authority userInfo user password host port relative path directory file query anchor'.split(' ')
  const DEFAULT_PORTS = {
    ftp: 21,
    http: 80,
    https: 443,
    mailto: 25,
  }
  const safeSvgDataUrlElements = ['img', 'video']
  const blockSvgDataUris = (allowSvgDataUrls, tagName) => {
    if (isNonNullable(allowSvgDataUrls)) {
      return !allowSvgDataUrls
    } else {
      return isNonNullable(tagName) ? !contains$2(safeSvgDataUrlElements, tagName) : true
    }
  }
  const decodeUri = (encodedUri) => {
    try {
      return decodeURIComponent(encodedUri)
    } catch (ex) {
      return unescape(encodedUri)
    }
  }
  const isInvalidUri = (settings, uri, tagName) => {
    const decodedUri = decodeUri(uri)
    if (settings.allow_script_urls) {
      return false
    } else if (/((java|vb)script|mhtml):/i.test(decodedUri)) {
      return true
    } else if (settings.allow_html_data_urls) {
      return false
    } else if (/^data:image\//i.test(decodedUri)) {
      return blockSvgDataUris(settings.allow_svg_data_urls, tagName) && /^data:image\/svg\+xml/i.test(decodedUri)
    } else {
      return /^data:/i.test(decodedUri)
    }
  }
  class URI {
    constructor(url, settings) {
      url = trim(url)
      this.settings = settings || {}
      const baseUri = this.settings.base_uri
      const self = this
      if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
        self.source = url
        return
      }
      const isProtocolRelative = url.indexOf('//') === 0
      if (url.indexOf('/') === 0 && !isProtocolRelative) {
        url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url
      }
      if (!/^[\w\-]*:?\/\//.test(url)) {
        const baseUrl = this.settings.base_uri ? this.settings.base_uri.path : new URI(document.location.href).directory
        if (this.settings.base_uri && this.settings.base_uri.protocol == '') {
          url = '//mce_host' + self.toAbsPath(baseUrl, url)
        } else {
          const match = /([^#?]*)([#?]?.*)/.exec(url)
          url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(baseUrl, match[1]) + match[2]
        }
      }
      url = url.replace(/@@/g, '(mce_at)')
      const urlMatch =
        /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?(\[[a-zA-Z0-9:.%]+\]|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(
          url
        )
      each$4(queryParts, (v, i) => {
        let part = urlMatch[i]
        if (part) {
          part = part.replace(/\(mce_at\)/g, '@@')
        }
        self[v] = part
      })
      if (baseUri) {
        if (!self.protocol) {
          self.protocol = baseUri.protocol
        }
        if (!self.userInfo) {
          self.userInfo = baseUri.userInfo
        }
        if (!self.port && self.host === 'mce_host') {
          self.port = baseUri.port
        }
        if (!self.host || self.host === 'mce_host') {
          self.host = baseUri.host
        }
        self.source = ''
      }
      if (isProtocolRelative) {
        self.protocol = ''
      }
    }
    static parseDataUri(uri) {
      let type
      const uriComponents = decodeURIComponent(uri).split(',')
      const matches = /data:([^;]+)/.exec(uriComponents[0])
      if (matches) {
        type = matches[1]
      }
      return {
        type,
        data: uriComponents[1],
      }
    }
    static isDomSafe(uri, context, options = {}) {
      if (options.allow_script_urls) {
        return true
      } else {
        const decodedUri = Entities.decode(uri).replace(/[\s\u0000-\u001F]+/g, '')
        return !isInvalidUri(options, decodedUri, context)
      }
    }
    static getDocumentBaseUrl(loc) {
      let baseUrl
      if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') {
        baseUrl = loc.href
      } else {
        baseUrl = loc.protocol + '//' + loc.host + loc.pathname
      }
      if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) {
        baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '')
        if (!/[\/\\]$/.test(baseUrl)) {
          baseUrl += '/'
        }
      }
      return baseUrl
    }
    setPath(path) {
      const pathMatch = /^(.*?)\/?(\w+)?$/.exec(path)
      this.path = pathMatch[0]
      this.directory = pathMatch[1]
      this.file = pathMatch[2]
      this.source = ''
      this.getURI()
    }
    toRelative(uri) {
      let output
      if (uri === './') {
        return uri
      }
      const relativeUri = new URI(uri, { base_uri: this })
      if (
        (relativeUri.host !== 'mce_host' && this.host !== relativeUri.host && relativeUri.host) ||
        this.port !== relativeUri.port ||
        (this.protocol !== relativeUri.protocol && relativeUri.protocol !== '')
      ) {
        return relativeUri.getURI()
      }
      const tu = this.getURI(),
        uu = relativeUri.getURI()
      if (tu === uu || (tu.charAt(tu.length - 1) === '/' && tu.substr(0, tu.length - 1) === uu)) {
        return tu
      }
      output = this.toRelPath(this.path, relativeUri.path)
      if (relativeUri.query) {
        output += '?' + relativeUri.query
      }
      if (relativeUri.anchor) {
        output += '#' + relativeUri.anchor
      }
      return output
    }
    toAbsolute(uri, noHost) {
      const absoluteUri = new URI(uri, { base_uri: this })
      return absoluteUri.getURI(noHost && this.isSameOrigin(absoluteUri))
    }
    isSameOrigin(uri) {
      if (this.host == uri.host && this.protocol == uri.protocol) {
        if (this.port == uri.port) {
          return true
        }
        const defaultPort = DEFAULT_PORTS[this.protocol]
        if (defaultPort && (this.port || defaultPort) == (uri.port || defaultPort)) {
          return true
        }
      }
      return false
    }
    toRelPath(base, path) {
      let breakPoint = 0,
        out = '',
        i,
        l
      const normalizedBase = base.substring(0, base.lastIndexOf('/')).split('/')
      const items = path.split('/')
      if (normalizedBase.length >= items.length) {
        for (i = 0, l = normalizedBase.length; i < l; i++) {
          if (i >= items.length || normalizedBase[i] !== items[i]) {
            breakPoint = i + 1
            break
          }
        }
      }
      if (normalizedBase.length < items.length) {
        for (i = 0, l = items.length; i < l; i++) {
          if (i >= normalizedBase.length || normalizedBase[i] !== items[i]) {
            breakPoint = i + 1
            break
          }
        }
      }
      if (breakPoint === 1) {
        return path
      }
      for (i = 0, l = normalizedBase.length - (breakPoint - 1); i < l; i++) {
        out += '../'
      }
      for (i = breakPoint - 1, l = items.length; i < l; i++) {
        if (i !== breakPoint - 1) {
          out += '/' + items[i]
        } else {
          out += items[i]
        }
      }
      return out
    }
    toAbsPath(base, path) {
      let i,
        nb = 0,
        o = [],
        outPath
      const tr = /\/$/.test(path) ? '/' : ''
      let normalizedBase = base.split('/')
      const normalizedPath = path.split('/')
      each$4(normalizedBase, (k) => {
        if (k) {
          o.push(k)
        }
      })
      normalizedBase = o
      for (i = normalizedPath.length - 1, o = []; i >= 0; i--) {
        if (normalizedPath[i].length === 0 || normalizedPath[i] === '.') {
          continue
        }
        if (normalizedPath[i] === '..') {
          nb++
          continue
        }
        if (nb > 0) {
          nb--
          continue
        }
        o.push(normalizedPath[i])
      }
      i = normalizedBase.length - nb
      if (i <= 0) {
        outPath = reverse(o).join('/')
      } else {
        outPath = normalizedBase.slice(0, i).join('/') + '/' + reverse(o).join('/')
      }
      if (outPath.indexOf('/') !== 0) {
        outPath = '/' + outPath
      }
      if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
        outPath += tr
      }
      return outPath
    }
    getURI(noProtoHost = false) {
      let s
      if (!this.source || noProtoHost) {
        s = ''
        if (!noProtoHost) {
          if (this.protocol) {
            s += this.protocol + '://'
          } else {
            s += '//'
          }
          if (this.userInfo) {
            s += this.userInfo + '@'
          }
          if (this.host) {
            s += this.host
          }
          if (this.port) {
            s += ':' + this.port
          }
        }
        if (this.path) {
          s += this.path
        }
        if (this.query) {
          s += '?' + this.query
        }
        if (this.anchor) {
          s += '#' + this.anchor
        }
        this.source = s
      }
      return this.source
    }
  }

  const makeMap = Tools.makeMap,
    extend$1 = Tools.extend
  const basePurifyConfig = {
    IN_PLACE: true,
    ALLOW_UNKNOWN_PROTOCOLS: true,
    ALLOWED_TAGS: ['#comment', '#cdata-section', 'body'],
    ALLOWED_ATTR: [],
  }
  const filteredUrlAttrs = Tools.makeMap('src,href,data,background,action,formaction,poster,xlink:href')
  const internalElementAttr = 'data-mce-type'
  const getPurifyConfig = (settings, mimeType) => {
    const config = { ...basePurifyConfig }
    config.PARSER_MEDIA_TYPE = mimeType
    if (settings.allow_script_urls) {
      config.ALLOWED_URI_REGEXP = /.*/
    } else if (settings.allow_html_data_urls) {
      config.ALLOWED_URI_REGEXP = /^(?!(\w+script|mhtml):)/i
    }
    return config
  }
  const setupPurify = (settings, schema) => {
    const purify$1 = purify()
    const specialElements = schema.getSpecialElements()
    const validate = settings.validate
    let uid = 0
    purify$1.addHook('uponSanitizeElement', (ele, evt) => {
      var _a, _b
      if (ele.nodeType === COMMENT && !settings.allow_conditional_comments && /^\[if/i.test(ele.nodeValue)) {
        ele.nodeValue = ' ' + ele.nodeValue
      }
      const tagName = evt.tagName
      if (ele.nodeType !== ELEMENT || tagName === 'body') {
        return
      }
      const element = SugarElement.fromDom(ele)
      const lcTagName = tagName.toLowerCase()
      const isInternalElement = has$1(element, internalElementAttr)
      const bogus = get$9(element, 'data-mce-bogus')
      if (!isInternalElement && isString(bogus)) {
        if (bogus === 'all') {
          remove$6(element)
        } else {
          unwrap(element)
        }
        return
      }
      const rule = schema.getElementRule(lcTagName)
      if (validate && !rule) {
        if (has$2(specialElements, lcTagName)) {
          remove$6(element)
        } else {
          unwrap(element)
        }
        return
      } else {
        evt.allowedTags[tagName] = true
      }
      if (validate && !isInternalElement) {
        each$f((_a = rule.attributesForced) !== null && _a !== void 0 ? _a : [], (attr) => {
          set$2(element, attr.name, attr.value === '{$uid}' ? `mce_${uid++}` : attr.value)
        })
        each$f((_b = rule.attributesDefault) !== null && _b !== void 0 ? _b : [], (attr) => {
          if (!has$1(element, attr.name)) {
            set$2(element, attr.name, attr.value === '{$uid}' ? `mce_${uid++}` : attr.value)
          }
        })
        if (rule.attributesRequired && !exists(rule.attributesRequired, (attr) => has$1(element, attr))) {
          unwrap(element)
          return
        }
        if (rule.removeEmptyAttrs && hasNone(element)) {
          unwrap(element)
          return
        }
        if (rule.outputName && rule.outputName !== lcTagName) {
          mutate(element, rule.outputName)
        }
      }
    })
    purify$1.addHook('uponSanitizeAttribute', (ele, evt) => {
      const tagName = ele.tagName.toLowerCase()
      const { attrName, attrValue } = evt
      evt.keepAttr =
        !validate || schema.isValid(tagName, attrName) || startsWith(attrName, 'data-') || startsWith(attrName, 'aria-')
      if (attrName in filteredUrlAttrs && isInvalidUri(settings, attrValue, tagName)) {
        evt.keepAttr = false
      }
      if (evt.keepAttr) {
        evt.allowedAttributes[attrName] = true
        if (attrName in schema.getBoolAttrs()) {
          evt.attrValue = attrName
        }
        if (settings.allow_svg_data_urls && startsWith(attrValue, 'data:image/svg+xml')) {
          evt.forceKeepAttr = true
        }
      } else if (
        ele.hasAttribute(internalElementAttr) &&
        (attrName === 'id' || attrName === 'class' || attrName === 'style')
      ) {
        evt.forceKeepAttr = true
      }
    })
    return purify$1
  }
  const transferChildren = (parent, nativeParent, specialElements) => {
    const parentName = parent.name
    const isSpecial = parentName in specialElements && parentName !== 'title' && parentName !== 'textarea'
    const childNodes = nativeParent.childNodes
    for (let ni = 0, nl = childNodes.length; ni < nl; ni++) {
      const nativeChild = childNodes[ni]
      const child = new AstNode(nativeChild.nodeName.toLowerCase(), nativeChild.nodeType)
      if (isElement$6(nativeChild)) {
        const attributes = nativeChild.attributes
        for (let ai = 0, al = attributes.length; ai < al; ai++) {
          const attr = attributes[ai]
          child.attr(attr.name, attr.value)
        }
      } else if (isText$9(nativeChild)) {
        child.value = nativeChild.data
        if (isSpecial) {
          child.raw = true
        }
      } else if (isComment(nativeChild) || isCData(nativeChild) || isPi(nativeChild)) {
        child.value = nativeChild.data
      }
      transferChildren(child, nativeChild, specialElements)
      parent.append(child)
    }
  }
  const walkTree = (root, preprocessors, postprocessors) => {
    const traverseOrder = []
    for (let node = root, lastNode = node; isNonNullable(node); lastNode = node, node = node.walk()) {
      each$f(preprocessors, (preprocess) => preprocess(node))
      if (isNullable(node.parent) && node !== root) {
        node = lastNode
      } else {
        traverseOrder.push(node)
      }
    }
    for (let i = traverseOrder.length - 1; i >= 0; i--) {
      const node = traverseOrder[i]
      each$f(postprocessors, (postprocess) => postprocess(node))
    }
  }
  const whitespaceCleaner = (root, schema, settings, args) => {
    const validate = settings.validate
    const nonEmptyElements = schema.getNonEmptyElements()
    const whitespaceElements = schema.getWhitespaceElements()
    const blockElements = extend$1(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements())
    const textRootBlockElements = getTextRootBlockElements(schema)
    const allWhiteSpaceRegExp = /[ \t\r\n]+/g
    const startWhiteSpaceRegExp = /^[ \t\r\n]+/
    const endWhiteSpaceRegExp = /[ \t\r\n]+$/
    const hasWhitespaceParent = (node) => {
      node = node.parent
      while (isNonNullable(node)) {
        if (node.name in whitespaceElements) {
          return true
        } else {
          node = node.parent
        }
      }
      return false
    }
    const isTextRootBlockEmpty = (node) => {
      let tempNode = node
      while (isNonNullable(tempNode)) {
        if (tempNode.name in textRootBlockElements) {
          return isEmpty(schema, nonEmptyElements, whitespaceElements, tempNode)
        } else {
          tempNode = tempNode.parent
        }
      }
      return false
    }
    const isAtEdgeOfBlock = (node, start) => {
      const neighbour = start ? node.prev : node.next
      if (isNonNullable(neighbour)) {
        return false
      }
      return node.parent.name in blockElements && (node.parent !== root || args.isRootContent)
    }
    const preprocess = (node) => {
      if (node.type === 3) {
        if (!hasWhitespaceParent(node)) {
          let text = node.value
          text = text.replace(allWhiteSpaceRegExp, ' ')
          if (isLineBreakNode(node.prev, blockElements) || isAtEdgeOfBlock(node, true)) {
            text = text.replace(startWhiteSpaceRegExp, '')
          }
          if (text.length === 0) {
            node.remove()
          } else {
            node.value = text
          }
        }
      }
    }
    const postprocess = (node) => {
      var _a
      if (node.type === 1) {
        const elementRule = schema.getElementRule(node.name)
        if (validate && elementRule) {
          const isNodeEmpty = isEmpty(schema, nonEmptyElements, whitespaceElements, node)
          if (elementRule.paddInEmptyBlock && isNodeEmpty && isTextRootBlockEmpty(node)) {
            paddEmptyNode(settings, args, blockElements, node)
          } else if (elementRule.removeEmpty && isNodeEmpty) {
            if (blockElements[node.name]) {
              node.remove()
            } else {
              node.unwrap()
            }
          } else if (elementRule.paddEmpty && (isNodeEmpty || isPaddedWithNbsp(node))) {
            paddEmptyNode(settings, args, blockElements, node)
          }
        }
      } else if (node.type === 3) {
        if (!hasWhitespaceParent(node)) {
          let text = node.value
          if (
            blockElements[(_a = node.next) === null || _a === void 0 ? void 0 : _a.name] ||
            isAtEdgeOfBlock(node, false)
          ) {
            text = text.replace(endWhiteSpaceRegExp, '')
          }
          if (text.length === 0) {
            node.remove()
          } else {
            node.value = text
          }
        }
      }
    }
    return [preprocess, postprocess]
  }
  const getRootBlockName = (settings, args) => {
    var _a
    const name = (_a = args.forced_root_block) !== null && _a !== void 0 ? _a : settings.forced_root_block
    if (name === false) {
      return ''
    } else if (name === true) {
      return 'p'
    } else {
      return name
    }
  }
  const DomParser = (settings = {}, schema = Schema()) => {
    const nodeFilterRegistry = create$7()
    const attributeFilterRegistry = create$7()
    const defaultedSettings = {
      validate: true,
      root_name: 'body',
      ...settings,
    }
    const parser = new DOMParser()
    const purify = setupPurify(defaultedSettings, schema)
    const parseAndSanitizeWithContext = (html, rootName, format = 'html') => {
      const mimeType = format === 'xhtml' ? 'application/xhtml+xml' : 'text/html'
      const isSpecialRoot = has$2(schema.getSpecialElements(), rootName.toLowerCase())
      const content = isSpecialRoot ? `<${rootName}>${html}</${rootName}>` : html
      const wrappedHtml =
        format === 'xhtml'
          ? `<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>${content}</body></html>`
          : `<body>${content}</body>`
      const body = parser.parseFromString(wrappedHtml, mimeType).body
      purify.sanitize(body, getPurifyConfig(defaultedSettings, mimeType))
      purify.removed = []
      return isSpecialRoot ? body.firstChild : body
    }
    const addNodeFilter = nodeFilterRegistry.addFilter
    const getNodeFilters = nodeFilterRegistry.getFilters
    const removeNodeFilter = nodeFilterRegistry.removeFilter
    const addAttributeFilter = attributeFilterRegistry.addFilter
    const getAttributeFilters = attributeFilterRegistry.getFilters
    const removeAttributeFilter = attributeFilterRegistry.removeFilter
    const findInvalidChildren = (node, invalidChildren) => {
      const parent = node.parent
      if (parent && schema.children[node.name] && !schema.isValidChild(parent.name, node.name)) {
        invalidChildren.push(node)
      }
    }
    const addRootBlocks = (rootNode, rootBlockName) => {
      const blockElements = extend$1(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements())
      const startWhiteSpaceRegExp = /^[ \t\r\n]+/
      const endWhiteSpaceRegExp = /[ \t\r\n]+$/
      let node = rootNode.firstChild,
        rootBlockNode = null
      const trim = (rootBlock) => {
        if (rootBlock) {
          node = rootBlock.firstChild
          if (node && node.type === 3) {
            node.value = node.value.replace(startWhiteSpaceRegExp, '')
          }
          node = rootBlock.lastChild
          if (node && node.type === 3) {
            node.value = node.value.replace(endWhiteSpaceRegExp, '')
          }
        }
      }
      if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
        return
      }
      while (node) {
        const next = node.next
        if (
          node.type === 3 ||
          (node.type === 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr(internalElementAttr))
        ) {
          if (!rootBlockNode) {
            rootBlockNode = new AstNode(rootBlockName, 1)
            rootBlockNode.attr(defaultedSettings.forced_root_block_attrs)
            rootNode.insert(rootBlockNode, node)
            rootBlockNode.append(node)
          } else {
            rootBlockNode.append(node)
          }
        } else {
          trim(rootBlockNode)
          rootBlockNode = null
        }
        node = next
      }
      trim(rootBlockNode)
    }
    const parse = (html, args = {}) => {
      var _a
      const validate = defaultedSettings.validate
      const rootName = (_a = args.context) !== null && _a !== void 0 ? _a : defaultedSettings.root_name
      const element = parseAndSanitizeWithContext(html, rootName, args.format)
      const rootNode = new AstNode(rootName, 11)
      transferChildren(rootNode, element, schema.getSpecialElements())
      const [whitespacePre, whitespacePost] = whitespaceCleaner(rootNode, schema, defaultedSettings, args)
      const invalidChildren = []
      const invalidFinder = validate ? (node) => findInvalidChildren(node, invalidChildren) : noop
      const matches = {
        nodes: {},
        attributes: {},
      }
      const matchFinder = (node) => matchNode$1(getNodeFilters(), getAttributeFilters(), node, matches)
      walkTree(rootNode, [whitespacePre, matchFinder], [whitespacePost, invalidFinder])
      invalidChildren.reverse()
      if (validate && invalidChildren.length > 0) {
        if (args.context) {
          const { pass: topLevelChildren, fail: otherChildren } = partition$2(
            invalidChildren,
            (child) => child.parent === rootNode
          )
          cleanInvalidNodes(otherChildren, schema, matchFinder)
          args.invalid = topLevelChildren.length > 0
        } else {
          cleanInvalidNodes(invalidChildren, schema, matchFinder)
        }
      }
      const rootBlockName = getRootBlockName(defaultedSettings, args)
      if (rootBlockName && (rootNode.name === 'body' || args.isRootContent)) {
        addRootBlocks(rootNode, rootBlockName)
      }
      if (!args.invalid) {
        runFilters(matches, args)
      }
      return rootNode
    }
    const exports = {
      schema,
      addAttributeFilter,
      getAttributeFilters,
      removeAttributeFilter,
      addNodeFilter,
      getNodeFilters,
      removeNodeFilter,
      parse,
    }
    register$4(exports, defaultedSettings)
    register$5(exports, defaultedSettings, schema)
    return exports
  }

  const serializeContent = (content) =>
    isTreeNode(content) ? HtmlSerializer({ validate: false }).serialize(content) : content
  const withSerializedContent = (content, fireEvent) => {
    const serializedContent = serializeContent(content)
    const eventArgs = fireEvent(serializedContent)
    if (eventArgs.isDefaultPrevented()) {
      return eventArgs
    } else if (isTreeNode(content)) {
      if (eventArgs.content !== serializedContent) {
        const rootNode = DomParser({
          validate: false,
          forced_root_block: false,
        }).parse(eventArgs.content, { context: content.name })
        return {
          ...eventArgs,
          content: rootNode,
        }
      } else {
        return {
          ...eventArgs,
          content,
        }
      }
    } else {
      return eventArgs
    }
  }
  const preProcessGetContent = (editor, args) => {
    if (args.no_events) {
      return Result.value(args)
    } else {
      const eventArgs = fireBeforeGetContent(editor, args)
      if (eventArgs.isDefaultPrevented()) {
        return Result.error(
          fireGetContent(editor, {
            content: '',
            ...eventArgs,
          }).content
        )
      } else {
        return Result.value(eventArgs)
      }
    }
  }
  const postProcessGetContent = (editor, content, args) => {
    if (args.no_events) {
      return content
    } else {
      const processedEventArgs = withSerializedContent(content, (c) =>
        fireGetContent(editor, {
          ...args,
          content: c,
        })
      )
      return processedEventArgs.content
    }
  }
  const preProcessSetContent = (editor, args) => {
    if (args.no_events) {
      return Result.value(args)
    } else {
      const processedEventArgs = withSerializedContent(args.content, (content) =>
        fireBeforeSetContent(editor, {
          ...args,
          content,
        })
      )
      if (processedEventArgs.isDefaultPrevented()) {
        fireSetContent(editor, processedEventArgs)
        return Result.error(undefined)
      } else {
        return Result.value(processedEventArgs)
      }
    }
  }
  const postProcessSetContent = (editor, content, args) => {
    if (!args.no_events) {
      fireSetContent(editor, {
        ...args,
        content,
      })
    }
  }

  const tableModel = (element, width, rows) => ({
    element,
    width,
    rows,
  })
  const tableRow = (element, cells) => ({
    element,
    cells,
  })
  const cellPosition = (x, y) => ({
    x,
    y,
  })
  const getSpan = (td, key) => {
    const value = parseInt(get$9(td, key), 10)
    return isNaN(value) ? 1 : value
  }
  const fillout = (table, x, y, tr, td) => {
    const rowspan = getSpan(td, 'rowspan')
    const colspan = getSpan(td, 'colspan')
    const rows = table.rows
    for (let y2 = y; y2 < y + rowspan; y2++) {
      if (!rows[y2]) {
        rows[y2] = tableRow(deep$1(tr), [])
      }
      for (let x2 = x; x2 < x + colspan; x2++) {
        const cells = rows[y2].cells
        cells[x2] = y2 === y && x2 === x ? td : shallow$1(td)
      }
    }
  }
  const cellExists = (table, x, y) => {
    const rows = table.rows
    const cells = rows[y] ? rows[y].cells : []
    return !!cells[x]
  }
  const skipCellsX = (table, x, y) => {
    while (cellExists(table, x, y)) {
      x++
    }
    return x
  }
  const getWidth = (rows) => {
    return foldl(
      rows,
      (acc, row) => {
        return row.cells.length > acc ? row.cells.length : acc
      },
      0
    )
  }
  const findElementPos = (table, element) => {
    const rows = table.rows
    for (let y = 0; y < rows.length; y++) {
      const cells = rows[y].cells
      for (let x = 0; x < cells.length; x++) {
        if (eq(cells[x], element)) {
          return Optional.some(cellPosition(x, y))
        }
      }
    }
    return Optional.none()
  }
  const extractRows = (table, sx, sy, ex, ey) => {
    const newRows = []
    const rows = table.rows
    for (let y = sy; y <= ey; y++) {
      const cells = rows[y].cells
      const slice = sx < ex ? cells.slice(sx, ex + 1) : cells.slice(ex, sx + 1)
      newRows.push(tableRow(rows[y].element, slice))
    }
    return newRows
  }
  const subTable = (table, startPos, endPos) => {
    const sx = startPos.x,
      sy = startPos.y
    const ex = endPos.x,
      ey = endPos.y
    const newRows = sy < ey ? extractRows(table, sx, sy, ex, ey) : extractRows(table, sx, ey, ex, sy)
    return tableModel(table.element, getWidth(newRows), newRows)
  }
  const createDomTable = (table, rows) => {
    const tableElement = shallow$1(table.element)
    const tableBody = SugarElement.fromTag('tbody')
    append(tableBody, rows)
    append$1(tableElement, tableBody)
    return tableElement
  }
  const modelRowsToDomRows = (table) => {
    return map$3(table.rows, (row) => {
      const cells = map$3(row.cells, (cell) => {
        const td = deep$1(cell)
        remove$b(td, 'colspan')
        remove$b(td, 'rowspan')
        return td
      })
      const tr = shallow$1(row.element)
      append(tr, cells)
      return tr
    })
  }
  const fromDom = (tableElm) => {
    const table = tableModel(shallow$1(tableElm), 0, [])
    each$f(descendants(tableElm, 'tr'), (tr, y) => {
      each$f(descendants(tr, 'td,th'), (td, x) => {
        fillout(table, skipCellsX(table, x, y), y, tr, td)
      })
    })
    return tableModel(table.element, getWidth(table.rows), table.rows)
  }
  const toDom = (table) => {
    return createDomTable(table, modelRowsToDomRows(table))
  }
  const subsection = (table, startElement, endElement) => {
    return findElementPos(table, startElement).bind((startPos) => {
      return findElementPos(table, endElement).map((endPos) => {
        return subTable(table, startPos, endPos)
      })
    })
  }

  const findParentListContainer = (parents) => find$2(parents, (elm) => name(elm) === 'ul' || name(elm) === 'ol')
  const getFullySelectedListWrappers = (parents, rng) =>
    find$2(parents, (elm) => name(elm) === 'li' && hasAllContentsSelected(elm, rng)).fold(constant([]), (_li) =>
      findParentListContainer(parents)
        .map((listCont) => {
          const listElm = SugarElement.fromTag(name(listCont))
          const listStyles = filter$5(getAllRaw(listCont), (_style, name) => startsWith(name, 'list-style'))
          setAll(listElm, listStyles)
          return [SugarElement.fromTag('li'), listElm]
        })
        .getOr([])
    )
  const wrap = (innerElm, elms) => {
    const wrapped = foldl(
      elms,
      (acc, elm) => {
        append$1(elm, acc)
        return elm
      },
      innerElm
    )
    return elms.length > 0 ? fromElements([wrapped]) : wrapped
  }
  const directListWrappers = (commonAnchorContainer) => {
    if (isListItem(commonAnchorContainer)) {
      return parent(commonAnchorContainer)
        .filter(isList)
        .fold(constant([]), (listElm) => [commonAnchorContainer, listElm])
    } else {
      return isList(commonAnchorContainer) ? [commonAnchorContainer] : []
    }
  }
  const getWrapElements = (rootNode, rng) => {
    const commonAnchorContainer = SugarElement.fromDom(rng.commonAncestorContainer)
    const parents = parentsAndSelf(commonAnchorContainer, rootNode)
    const wrapElements = filter$6(parents, isWrapElement)
    const listWrappers = getFullySelectedListWrappers(parents, rng)
    const allWrappers = wrapElements.concat(
      listWrappers.length ? listWrappers : directListWrappers(commonAnchorContainer)
    )
    return map$3(allWrappers, shallow$1)
  }
  const emptyFragment = () => fromElements([])
  const getFragmentFromRange = (rootNode, rng) =>
    wrap(SugarElement.fromDom(rng.cloneContents()), getWrapElements(rootNode, rng))
  const getParentTable = (rootElm, cell) => ancestor$2(cell, 'table', curry(eq, rootElm))
  const getTableFragment = (rootNode, selectedTableCells) =>
    getParentTable(rootNode, selectedTableCells[0])
      .bind((tableElm) => {
        const firstCell = selectedTableCells[0]
        const lastCell = selectedTableCells[selectedTableCells.length - 1]
        const fullTableModel = fromDom(tableElm)
        return subsection(fullTableModel, firstCell, lastCell).map((sectionedTableModel) =>
          fromElements([toDom(sectionedTableModel)])
        )
      })
      .getOrThunk(emptyFragment)
  const getSelectionFragment = (rootNode, ranges) =>
    ranges.length > 0 && ranges[0].collapsed ? emptyFragment() : getFragmentFromRange(rootNode, ranges[0])
  const read$3 = (rootNode, ranges) => {
    const selectedCells = getCellsFromElementOrRanges(ranges, rootNode)
    return selectedCells.length > 0 ? getTableFragment(rootNode, selectedCells) : getSelectionFragment(rootNode, ranges)
  }

  const isCollapsibleWhitespace = (text, index) => index >= 0 && index < text.length && isWhiteSpace(text.charAt(index))
  const getInnerText = (bin) => {
    return trim$1(bin.innerText)
  }
  const getContextNodeName = (parentBlockOpt) =>
    parentBlockOpt
      .map((block) => block.nodeName)
      .getOr('div')
      .toLowerCase()
  const getTextContent = (editor) =>
    Optional.from(editor.selection.getRng())
      .map((rng) => {
        const parentBlockOpt = Optional.from(editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock))
        const body = editor.getBody()
        const contextNodeName = getContextNodeName(parentBlockOpt)
        const bin = editor.dom.add(
          body,
          contextNodeName,
          {
            'data-mce-bogus': 'all',
            'style': 'overflow: hidden; opacity: 0;',
          },
          rng.cloneContents()
        )
        const text = getInnerText(bin)
        const nonRenderedText = trim$1(bin.textContent)
        editor.dom.remove(bin)
        if (
          isCollapsibleWhitespace(nonRenderedText, 0) ||
          isCollapsibleWhitespace(nonRenderedText, nonRenderedText.length - 1)
        ) {
          const parentBlock = parentBlockOpt.getOr(body)
          const parentBlockText = getInnerText(parentBlock)
          const textIndex = parentBlockText.indexOf(text)
          if (textIndex === -1) {
            return text
          } else {
            const hasProceedingSpace = isCollapsibleWhitespace(parentBlockText, textIndex - 1)
            const hasTrailingSpace = isCollapsibleWhitespace(parentBlockText, textIndex + text.length)
            return (hasProceedingSpace ? ' ' : '') + text + (hasTrailingSpace ? ' ' : '')
          }
        } else {
          return text
        }
      })
      .getOr('')
  const getSerializedContent = (editor, args) => {
    const rng = editor.selection.getRng(),
      tmpElm = editor.dom.create('body')
    const sel = editor.selection.getSel()
    const ranges = processRanges(editor, getRanges$1(sel))
    const fragment = args.contextual ? read$3(SugarElement.fromDom(editor.getBody()), ranges).dom : rng.cloneContents()
    if (fragment) {
      tmpElm.appendChild(fragment)
    }
    return editor.selection.serializer.serialize(tmpElm, args)
  }
  const extractSelectedContent = (editor, args) => {
    if (args.format === 'text') {
      return getTextContent(editor)
    } else {
      const content = getSerializedContent(editor, args)
      if (args.format === 'tree') {
        return content
      } else {
        return editor.selection.isCollapsed() ? '' : content
      }
    }
  }
  const setupArgs$3 = (args, format) => ({
    ...args,
    format,
    get: true,
    selection: true,
    getInner: true,
  })
  const getSelectedContentInternal = (editor, format, args = {}) => {
    const defaultedArgs = setupArgs$3(args, format)
    return preProcessGetContent(editor, defaultedArgs).fold(identity, (updatedArgs) => {
      const content = extractSelectedContent(editor, updatedArgs)
      return postProcessGetContent(editor, content, updatedArgs)
    })
  }

  const KEEP = 0,
    INSERT = 1,
    DELETE = 2
  const diff = (left, right) => {
    const size = left.length + right.length + 2
    const vDown = new Array(size)
    const vUp = new Array(size)
    const snake = (start, end, diag) => {
      return {
        start,
        end,
        diag,
      }
    }
    const buildScript = (start1, end1, start2, end2, script) => {
      const middle = getMiddleSnake(start1, end1, start2, end2)
      if (
        middle === null ||
        (middle.start === end1 && middle.diag === end1 - end2) ||
        (middle.end === start1 && middle.diag === start1 - start2)
      ) {
        let i = start1
        let j = start2
        while (i < end1 || j < end2) {
          if (i < end1 && j < end2 && left[i] === right[j]) {
            script.push([KEEP, left[i]])
            ++i
            ++j
          } else {
            if (end1 - start1 > end2 - start2) {
              script.push([DELETE, left[i]])
              ++i
            } else {
              script.push([INSERT, right[j]])
              ++j
            }
          }
        }
      } else {
        buildScript(start1, middle.start, start2, middle.start - middle.diag, script)
        for (let i2 = middle.start; i2 < middle.end; ++i2) {
          script.push([KEEP, left[i2]])
        }
        buildScript(middle.end, end1, middle.end - middle.diag, end2, script)
      }
    }
    const buildSnake = (start, diag, end1, end2) => {
      let end = start
      while (end - diag < end2 && end < end1 && left[end] === right[end - diag]) {
        ++end
      }
      return snake(start, end, diag)
    }
    const getMiddleSnake = (start1, end1, start2, end2) => {
      const m = end1 - start1
      const n = end2 - start2
      if (m === 0 || n === 0) {
        return null
      }
      const delta = m - n
      const sum = n + m
      const offset = (sum % 2 === 0 ? sum : sum + 1) / 2
      vDown[1 + offset] = start1
      vUp[1 + offset] = end1 + 1
      let d, k, i, x, y
      for (d = 0; d <= offset; ++d) {
        for (k = -d; k <= d; k += 2) {
          i = k + offset
          if (k === -d || (k !== d && vDown[i - 1] < vDown[i + 1])) {
            vDown[i] = vDown[i + 1]
          } else {
            vDown[i] = vDown[i - 1] + 1
          }
          x = vDown[i]
          y = x - start1 + start2 - k
          while (x < end1 && y < end2 && left[x] === right[y]) {
            vDown[i] = ++x
            ++y
          }
          if (delta % 2 !== 0 && delta - d <= k && k <= delta + d) {
            if (vUp[i - delta] <= vDown[i]) {
              return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2)
            }
          }
        }
        for (k = delta - d; k <= delta + d; k += 2) {
          i = k + offset - delta
          if (k === delta - d || (k !== delta + d && vUp[i + 1] <= vUp[i - 1])) {
            vUp[i] = vUp[i + 1] - 1
          } else {
            vUp[i] = vUp[i - 1]
          }
          x = vUp[i] - 1
          y = x - start1 + start2 - k
          while (x >= start1 && y >= start2 && left[x] === right[y]) {
            vUp[i] = x--
            y--
          }
          if (delta % 2 === 0 && -d <= k && k <= d) {
            if (vUp[i] <= vDown[i + delta]) {
              return buildSnake(vUp[i], k + start1 - start2, end1, end2)
            }
          }
        }
      }
    }
    const script = []
    buildScript(0, left.length, 0, right.length, script)
    return script
  }

  const getOuterHtml = (elm) => {
    if (isElement$6(elm)) {
      return elm.outerHTML
    } else if (isText$9(elm)) {
      return Entities.encodeRaw(elm.data, false)
    } else if (isComment(elm)) {
      return '<!--' + elm.data + '-->'
    }
    return ''
  }
  const createFragment = (html) => {
    let node
    const container = document.createElement('div')
    const frag = document.createDocumentFragment()
    if (html) {
      container.innerHTML = html
    }
    while ((node = container.firstChild)) {
      frag.appendChild(node)
    }
    return frag
  }
  const insertAt = (elm, html, index) => {
    const fragment = createFragment(html)
    if (elm.hasChildNodes() && index < elm.childNodes.length) {
      const target = elm.childNodes[index]
      target.parentNode.insertBefore(fragment, target)
    } else {
      elm.appendChild(fragment)
    }
  }
  const removeAt = (elm, index) => {
    if (elm.hasChildNodes() && index < elm.childNodes.length) {
      const target = elm.childNodes[index]
      target.parentNode.removeChild(target)
    }
  }
  const applyDiff = (diff, elm) => {
    let index = 0
    each$f(diff, (action) => {
      if (action[0] === KEEP) {
        index++
      } else if (action[0] === INSERT) {
        insertAt(elm, action[1], index)
        index++
      } else if (action[0] === DELETE) {
        removeAt(elm, index)
      }
    })
  }
  const read$2 = (elm) => {
    return filter$6(map$3(from(elm.childNodes), getOuterHtml), (item) => {
      return item.length > 0
    })
  }
  const write = (fragments, elm) => {
    const currentFragments = map$3(from(elm.childNodes), getOuterHtml)
    applyDiff(diff(currentFragments, fragments), elm)
    return elm
  }

  const lazyTempDocument = cached(() => document.implementation.createHTMLDocument('undo'))
  const hasIframes = (html) => {
    return html.indexOf('</iframe>') !== -1
  }
  const createFragmentedLevel = (fragments) => {
    return {
      type: 'fragmented',
      fragments,
      content: '',
      bookmark: null,
      beforeBookmark: null,
    }
  }
  const createCompleteLevel = (content) => {
    return {
      type: 'complete',
      fragments: null,
      content,
      bookmark: null,
      beforeBookmark: null,
    }
  }
  const createFromEditor = (editor) => {
    const fragments = read$2(editor.getBody())
    const trimmedFragments = bind$3(fragments, (html) => {
      const trimmed = trimInternal(editor.serializer, html)
      return trimmed.length > 0 ? [trimmed] : []
    })
    const content = trimmedFragments.join('')
    return hasIframes(content) ? createFragmentedLevel(trimmedFragments) : createCompleteLevel(content)
  }
  const applyToEditor = (editor, level, before) => {
    const bookmark = before ? level.beforeBookmark : level.bookmark
    if (level.type === 'fragmented') {
      write(level.fragments, editor.getBody())
    } else {
      editor.setContent(level.content, {
        format: 'raw',
        no_selection: isNonNullable(bookmark) && isPathBookmark(bookmark) ? !bookmark.isFakeCaret : true,
      })
    }
    editor.selection.moveToBookmark(bookmark)
  }
  const getLevelContent = (level) => {
    return level.type === 'fragmented' ? level.fragments.join('') : level.content
  }
  const getCleanLevelContent = (level) => {
    const elm = SugarElement.fromTag('body', lazyTempDocument())
    set(elm, getLevelContent(level))
    each$f(descendants(elm, '*[data-mce-bogus]'), unwrap)
    return get$6(elm)
  }
  const hasEqualContent = (level1, level2) => getLevelContent(level1) === getLevelContent(level2)
  const hasEqualCleanedContent = (level1, level2) => getCleanLevelContent(level1) === getCleanLevelContent(level2)
  const isEq$1 = (level1, level2) => {
    if (!level1 || !level2) {
      return false
    } else if (hasEqualContent(level1, level2)) {
      return true
    } else {
      return hasEqualCleanedContent(level1, level2)
    }
  }

  const isUnlocked = (locks) => locks.get() === 0

  const setTyping = (undoManager, typing, locks) => {
    if (isUnlocked(locks)) {
      undoManager.typing = typing
    }
  }
  const endTyping = (undoManager, locks) => {
    if (undoManager.typing) {
      setTyping(undoManager, false, locks)
      undoManager.add()
    }
  }
  const endTypingLevelIgnoreLocks = (undoManager) => {
    if (undoManager.typing) {
      undoManager.typing = false
      undoManager.add()
    }
  }

  const beforeChange$1 = (editor, locks, beforeBookmark) => {
    if (isUnlocked(locks)) {
      beforeBookmark.set(getUndoBookmark(editor.selection))
    }
  }
  const addUndoLevel$1 = (editor, undoManager, index, locks, beforeBookmark, level, event) => {
    const currentLevel = createFromEditor(editor)
    level = level || {}
    level = Tools.extend(level, currentLevel)
    if (isUnlocked(locks) === false || editor.removed) {
      return null
    }
    const lastLevel = undoManager.data[index.get()]
    if (
      editor
        .dispatch('BeforeAddUndo', {
          level,
          lastLevel,
          originalEvent: event,
        })
        .isDefaultPrevented()
    ) {
      return null
    }
    if (lastLevel && isEq$1(lastLevel, level)) {
      return null
    }
    if (undoManager.data[index.get()]) {
      beforeBookmark.get().each((bm) => {
        undoManager.data[index.get()].beforeBookmark = bm
      })
    }
    const customUndoRedoLevels = getCustomUndoRedoLevels(editor)
    if (customUndoRedoLevels) {
      if (undoManager.data.length > customUndoRedoLevels) {
        for (let i = 0; i < undoManager.data.length - 1; i++) {
          undoManager.data[i] = undoManager.data[i + 1]
        }
        undoManager.data.length--
        index.set(undoManager.data.length)
      }
    }
    level.bookmark = getUndoBookmark(editor.selection)
    if (index.get() < undoManager.data.length - 1) {
      undoManager.data.length = index.get() + 1
    }
    undoManager.data.push(level)
    index.set(undoManager.data.length - 1)
    const args = {
      level,
      lastLevel,
      originalEvent: event,
    }
    if (index.get() > 0) {
      editor.setDirty(true)
      editor.dispatch('AddUndo', args)
      editor.dispatch('change', args)
    } else {
      editor.dispatch('AddUndo', args)
    }
    return level
  }
  const clear$1 = (editor, undoManager, index) => {
    undoManager.data = []
    index.set(0)
    undoManager.typing = false
    editor.dispatch('ClearUndos')
  }
  const extra$1 = (editor, undoManager, index, callback1, callback2) => {
    if (undoManager.transact(callback1)) {
      const bookmark = undoManager.data[index.get()].bookmark
      const lastLevel = undoManager.data[index.get() - 1]
      applyToEditor(editor, lastLevel, true)
      if (undoManager.transact(callback2)) {
        undoManager.data[index.get() - 1].beforeBookmark = bookmark
      }
    }
  }
  const redo$1 = (editor, index, data) => {
    let level
    if (index.get() < data.length - 1) {
      index.set(index.get() + 1)
      level = data[index.get()]
      applyToEditor(editor, level, false)
      editor.setDirty(true)
      editor.dispatch('Redo', { level })
    }
    return level
  }
  const undo$1 = (editor, undoManager, locks, index) => {
    let level
    if (undoManager.typing) {
      undoManager.add()
      undoManager.typing = false
      setTyping(undoManager, false, locks)
    }
    if (index.get() > 0) {
      index.set(index.get() - 1)
      level = undoManager.data[index.get()]
      applyToEditor(editor, level, true)
      editor.setDirty(true)
      editor.dispatch('Undo', { level })
    }
    return level
  }
  const reset$1 = (undoManager) => {
    undoManager.clear()
    undoManager.add()
  }
  const hasUndo$1 = (editor, undoManager, index) =>
    index.get() > 0 ||
    (undoManager.typing && undoManager.data[0] && !isEq$1(createFromEditor(editor), undoManager.data[0]))
  const hasRedo$1 = (undoManager, index) => index.get() < undoManager.data.length - 1 && !undoManager.typing
  const transact$1 = (undoManager, locks, callback) => {
    endTyping(undoManager, locks)
    undoManager.beforeChange()
    undoManager.ignore(callback)
    return undoManager.add()
  }
  const ignore$1 = (locks, callback) => {
    try {
      locks.set(locks.get() + 1)
      callback()
    } finally {
      locks.set(locks.get() - 1)
    }
  }

  const addVisualInternal = (editor, elm) => {
    const dom = editor.dom
    const scope = isNonNullable(elm) ? elm : editor.getBody()
    if (isUndefined(editor.hasVisual)) {
      editor.hasVisual = isVisualAidsEnabled(editor)
    }
    each$f(dom.select('table,a', scope), (matchedElm) => {
      switch (matchedElm.nodeName) {
        case 'TABLE':
          const cls = getVisualAidsTableClass(editor)
          const value = dom.getAttrib(matchedElm, 'border')
          if ((!value || value === '0') && editor.hasVisual) {
            dom.addClass(matchedElm, cls)
          } else {
            dom.removeClass(matchedElm, cls)
          }
          break
        case 'A':
          if (!dom.getAttrib(matchedElm, 'href')) {
            const value = dom.getAttrib(matchedElm, 'name') || matchedElm.id
            const cls = getVisualAidsAnchorClass(editor)
            if (value && editor.hasVisual) {
              dom.addClass(matchedElm, cls)
            } else {
              dom.removeClass(matchedElm, cls)
            }
          }
          break
      }
    })
    editor.dispatch('VisualAid', {
      element: elm,
      hasVisual: editor.hasVisual,
    })
  }

  const makePlainAdaptor = (editor) => ({
    init: { bindEvents: noop },
    undoManager: {
      beforeChange: (locks, beforeBookmark) => beforeChange$1(editor, locks, beforeBookmark),
      add: (undoManager, index, locks, beforeBookmark, level, event) =>
        addUndoLevel$1(editor, undoManager, index, locks, beforeBookmark, level, event),
      undo: (undoManager, locks, index) => undo$1(editor, undoManager, locks, index),
      redo: (index, data) => redo$1(editor, index, data),
      clear: (undoManager, index) => clear$1(editor, undoManager, index),
      reset: (undoManager) => reset$1(undoManager),
      hasUndo: (undoManager, index) => hasUndo$1(editor, undoManager, index),
      hasRedo: (undoManager, index) => hasRedo$1(undoManager, index),
      transact: (undoManager, locks, callback) => transact$1(undoManager, locks, callback),
      ignore: (locks, callback) => ignore$1(locks, callback),
      extra: (undoManager, index, callback1, callback2) => extra$1(editor, undoManager, index, callback1, callback2),
    },
    formatter: {
      match: (name, vars, node, similar) => match$2(editor, name, vars, node, similar),
      matchAll: (names, vars) => matchAll(editor, names, vars),
      matchNode: (node, name, vars, similar) => matchNode(editor, node, name, vars, similar),
      canApply: (name) => canApply(editor, name),
      closest: (names) => closest$1(editor, names),
      apply: (name, vars, node) => applyFormat$1(editor, name, vars, node),
      remove: (name, vars, node, similar) => remove$2(editor, name, vars, node, similar),
      toggle: (name, vars, node) => toggle(editor, name, vars, node),
      formatChanged: (registeredFormatListeners, formats, callback, similar, vars) =>
        formatChangedInternal(editor, registeredFormatListeners, formats, callback, similar, vars),
    },
    editor: {
      getContent: (args) => getContentInternal(editor, args),
      setContent: (content, args) => setContentInternal(editor, content, args),
      insertContent: (value, details) => insertHtmlAtCaret(editor, value, details),
      addVisual: (elm) => addVisualInternal(editor, elm),
    },
    selection: { getContent: (format, args) => getSelectedContentInternal(editor, format, args) },
    autocompleter: {
      addDecoration: (range) => create$9(editor, range),
      removeDecoration: () => remove$3(editor, SugarElement.fromDom(editor.getBody())),
    },
    raw: { getModel: () => Optional.none() },
  })
  const makeRtcAdaptor = (rtcEditor) => {
    const defaultVars = (vars) => (isObject(vars) ? vars : {})
    const { init, undoManager, formatter, editor, selection, autocompleter, raw } = rtcEditor
    return {
      init: { bindEvents: init.bindEvents },
      undoManager: {
        beforeChange: undoManager.beforeChange,
        add: undoManager.add,
        undo: undoManager.undo,
        redo: undoManager.redo,
        clear: undoManager.clear,
        reset: undoManager.reset,
        hasUndo: undoManager.hasUndo,
        hasRedo: undoManager.hasRedo,
        transact: (_undoManager, _locks, fn) => undoManager.transact(fn),
        ignore: (_locks, callback) => undoManager.ignore(callback),
        extra: (_undoManager, _index, callback1, callback2) => undoManager.extra(callback1, callback2),
      },
      formatter: {
        match: (name, vars, _node, similar) => formatter.match(name, defaultVars(vars), similar),
        matchAll: formatter.matchAll,
        matchNode: formatter.matchNode,
        canApply: (name) => formatter.canApply(name),
        closest: (names) => formatter.closest(names),
        apply: (name, vars, _node) => formatter.apply(name, defaultVars(vars)),
        remove: (name, vars, _node, _similar) => formatter.remove(name, defaultVars(vars)),
        toggle: (name, vars, _node) => formatter.toggle(name, defaultVars(vars)),
        formatChanged: (_rfl, formats, callback, similar, vars) =>
          formatter.formatChanged(formats, callback, similar, vars),
      },
      editor: {
        getContent: (args) => editor.getContent(args),
        setContent: (content, args) => {
          return {
            content: editor.setContent(content, args),
            html: '',
          }
        },
        insertContent: (content, _details) => {
          editor.insertContent(content)
          return ''
        },
        addVisual: editor.addVisual,
      },
      selection: { getContent: (_format, args) => selection.getContent(args) },
      autocompleter: {
        addDecoration: autocompleter.addDecoration,
        removeDecoration: autocompleter.removeDecoration,
      },
      raw: { getModel: () => Optional.some(raw.getRawModel()) },
    }
  }
  const makeNoopAdaptor = () => {
    const nul = constant(null)
    const empty = constant('')
    return {
      init: { bindEvents: noop },
      undoManager: {
        beforeChange: noop,
        add: nul,
        undo: nul,
        redo: nul,
        clear: noop,
        reset: noop,
        hasUndo: never,
        hasRedo: never,
        transact: nul,
        ignore: noop,
        extra: noop,
      },
      formatter: {
        match: never,
        matchAll: constant([]),
        matchNode: constant(undefined),
        canApply: never,
        closest: empty,
        apply: noop,
        remove: noop,
        toggle: noop,
        formatChanged: constant({ unbind: noop }),
      },
      editor: {
        getContent: empty,
        setContent: constant({
          content: '',
          html: '',
        }),
        insertContent: constant(''),
        addVisual: noop,
      },
      selection: { getContent: empty },
      autocompleter: {
        addDecoration: noop,
        removeDecoration: noop,
      },
      raw: { getModel: constant(Optional.none()) },
    }
  }
  const isRtc = (editor) => has$2(editor.plugins, 'rtc')
  const getRtcSetup = (editor) => get$a(editor.plugins, 'rtc').bind((rtcPlugin) => Optional.from(rtcPlugin.setup))
  const setup$s = (editor) => {
    const editorCast = editor
    return getRtcSetup(editor).fold(
      () => {
        editorCast.rtcInstance = makePlainAdaptor(editor)
        return Optional.none()
      },
      (setup) => {
        editorCast.rtcInstance = makeNoopAdaptor()
        return Optional.some(() =>
          setup().then((rtcEditor) => {
            editorCast.rtcInstance = makeRtcAdaptor(rtcEditor)
            return rtcEditor.rtc.isRemote
          })
        )
      }
    )
  }
  const getRtcInstanceWithFallback = (editor) => (editor.rtcInstance ? editor.rtcInstance : makePlainAdaptor(editor))
  const getRtcInstanceWithError = (editor) => {
    const rtcInstance = editor.rtcInstance
    if (!rtcInstance) {
      throw new Error('Failed to get RTC instance not yet initialized.')
    } else {
      return rtcInstance
    }
  }
  const beforeChange = (editor, locks, beforeBookmark) => {
    getRtcInstanceWithError(editor).undoManager.beforeChange(locks, beforeBookmark)
  }
  const addUndoLevel = (editor, undoManager, index, locks, beforeBookmark, level, event) =>
    getRtcInstanceWithError(editor).undoManager.add(undoManager, index, locks, beforeBookmark, level, event)
  const undo = (editor, undoManager, locks, index) =>
    getRtcInstanceWithError(editor).undoManager.undo(undoManager, locks, index)
  const redo = (editor, index, data) => getRtcInstanceWithError(editor).undoManager.redo(index, data)
  const clear = (editor, undoManager, index) => {
    getRtcInstanceWithError(editor).undoManager.clear(undoManager, index)
  }
  const reset = (editor, undoManager) => {
    getRtcInstanceWithError(editor).undoManager.reset(undoManager)
  }
  const hasUndo = (editor, undoManager, index) =>
    getRtcInstanceWithError(editor).undoManager.hasUndo(undoManager, index)
  const hasRedo = (editor, undoManager, index) =>
    getRtcInstanceWithError(editor).undoManager.hasRedo(undoManager, index)
  const transact = (editor, undoManager, locks, callback) =>
    getRtcInstanceWithError(editor).undoManager.transact(undoManager, locks, callback)
  const ignore = (editor, locks, callback) => {
    getRtcInstanceWithError(editor).undoManager.ignore(locks, callback)
  }
  const extra = (editor, undoManager, index, callback1, callback2) => {
    getRtcInstanceWithError(editor).undoManager.extra(undoManager, index, callback1, callback2)
  }
  const matchFormat = (editor, name, vars, node, similar) =>
    getRtcInstanceWithError(editor).formatter.match(name, vars, node, similar)
  const matchAllFormats = (editor, names, vars) => getRtcInstanceWithError(editor).formatter.matchAll(names, vars)
  const matchNodeFormat = (editor, node, name, vars, similar) =>
    getRtcInstanceWithError(editor).formatter.matchNode(node, name, vars, similar)
  const canApplyFormat = (editor, name) => getRtcInstanceWithError(editor).formatter.canApply(name)
  const closestFormat = (editor, names) => getRtcInstanceWithError(editor).formatter.closest(names)
  const applyFormat = (editor, name, vars, node) => {
    getRtcInstanceWithError(editor).formatter.apply(name, vars, node)
  }
  const removeFormat = (editor, name, vars, node, similar) => {
    getRtcInstanceWithError(editor).formatter.remove(name, vars, node, similar)
  }
  const toggleFormat = (editor, name, vars, node) => {
    getRtcInstanceWithError(editor).formatter.toggle(name, vars, node)
  }
  const formatChanged = (editor, registeredFormatListeners, formats, callback, similar, vars) =>
    getRtcInstanceWithError(editor).formatter.formatChanged(registeredFormatListeners, formats, callback, similar, vars)
  const getContent$2 = (editor, args) => getRtcInstanceWithFallback(editor).editor.getContent(args)
  const setContent$2 = (editor, content, args) => getRtcInstanceWithFallback(editor).editor.setContent(content, args)
  const insertContent$1 = (editor, value, details) =>
    getRtcInstanceWithFallback(editor).editor.insertContent(value, details)
  const getSelectedContent = (editor, format, args) =>
    getRtcInstanceWithError(editor).selection.getContent(format, args)
  const addVisual$1 = (editor, elm) => getRtcInstanceWithError(editor).editor.addVisual(elm)
  const bindEvents = (editor) => getRtcInstanceWithError(editor).init.bindEvents()
  const addAutocompleterDecoration = (editor, range) =>
    getRtcInstanceWithError(editor).autocompleter.addDecoration(range)
  const removeAutocompleterDecoration = (editor) => getRtcInstanceWithError(editor).autocompleter.removeDecoration()

  const getContent$1 = (editor, args = {}) => {
    const format = args.format ? args.format : 'html'
    return getSelectedContent(editor, format, args)
  }

  const removeEmpty = (text) => {
    if (text.dom.length === 0) {
      remove$6(text)
      return Optional.none()
    } else {
      return Optional.some(text)
    }
  }
  const walkPastBookmark = (node, start) =>
    node.filter((elm) => BookmarkManager.isBookmarkNode(elm.dom)).bind(start ? nextSibling : prevSibling)
  const merge$1 = (outer, inner, rng, start) => {
    const outerElm = outer.dom
    const innerElm = inner.dom
    const oldLength = start ? outerElm.length : innerElm.length
    if (start) {
      mergeTextNodes(outerElm, innerElm, false, !start)
      rng.setStart(innerElm, oldLength)
    } else {
      mergeTextNodes(innerElm, outerElm, false, !start)
      rng.setEnd(innerElm, oldLength)
    }
  }
  const normalizeTextIfRequired = (inner, start) => {
    parent(inner).each((root) => {
      const text = inner.dom
      if (start && needsToBeNbspLeft(root, CaretPosition(text, 0))) {
        normalizeWhitespaceAfter(text, 0)
      } else if (!start && needsToBeNbspRight(root, CaretPosition(text, text.length))) {
        normalizeWhitespaceBefore(text, text.length)
      }
    })
  }
  const mergeAndNormalizeText = (outerNode, innerNode, rng, start) => {
    outerNode
      .bind((outer) => {
        const normalizer = start ? normalizeWhitespaceBefore : normalizeWhitespaceAfter
        normalizer(outer.dom, start ? outer.dom.length : 0)
        return innerNode.filter(isText$a).map((inner) => merge$1(outer, inner, rng, start))
      })
      .orThunk(() => {
        const innerTextNode = walkPastBookmark(innerNode, start).or(innerNode).filter(isText$a)
        return innerTextNode.map((inner) => normalizeTextIfRequired(inner, start))
      })
  }
  const rngSetContent = (rng, fragment) => {
    const firstChild = Optional.from(fragment.firstChild).map(SugarElement.fromDom)
    const lastChild = Optional.from(fragment.lastChild).map(SugarElement.fromDom)
    rng.deleteContents()
    rng.insertNode(fragment)
    const prevText = firstChild.bind(prevSibling).filter(isText$a).bind(removeEmpty)
    const nextText = lastChild.bind(nextSibling).filter(isText$a).bind(removeEmpty)
    mergeAndNormalizeText(prevText, firstChild, rng, true)
    mergeAndNormalizeText(nextText, lastChild, rng, false)
    rng.collapse(false)
  }
  const setupArgs$2 = (args, content) => ({
    format: 'html',
    ...args,
    set: true,
    selection: true,
    content,
  })
  const cleanContent = (editor, args) => {
    if (args.format !== 'raw') {
      const rng = editor.selection.getRng()
      const contextBlock = editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock)
      const contextArgs = contextBlock ? { context: contextBlock.nodeName.toLowerCase() } : {}
      const node = editor.parser.parse(args.content, {
        forced_root_block: false,
        ...contextArgs,
        ...args,
      })
      return HtmlSerializer({ validate: false }, editor.schema).serialize(node)
    } else {
      return args.content
    }
  }
  const setContent$1 = (editor, content, args = {}) => {
    const defaultedArgs = setupArgs$2(args, content)
    preProcessSetContent(editor, defaultedArgs).each((updatedArgs) => {
      const cleanedContent = cleanContent(editor, updatedArgs)
      const rng = editor.selection.getRng()
      rngSetContent(rng, rng.createContextualFragment(cleanedContent))
      editor.selection.setRng(rng)
      scrollRangeIntoView(editor, rng)
      postProcessSetContent(editor, cleanedContent, updatedArgs)
    })
  }

  const deleteFromCallbackMap = (callbackMap, selector, callback) => {
    if (callbackMap && has$2(callbackMap, selector)) {
      const newCallbacks = filter$6(callbackMap[selector], (cb) => cb !== callback)
      if (newCallbacks.length === 0) {
        delete callbackMap[selector]
      } else {
        callbackMap[selector] = newCallbacks
      }
    }
  }
  var SelectorChanged = (dom, editor) => {
    let selectorChangedData
    let currentSelectors
    const findMatchingNode = (selector, nodes) => find$2(nodes, (node) => dom.is(node, selector))
    const getParents = (elem) => dom.getParents(elem, null, dom.getRoot())
    return {
      selectorChangedWithUnbind: (selector, callback) => {
        if (!selectorChangedData) {
          selectorChangedData = {}
          currentSelectors = {}
          editor.on('NodeChange', (e) => {
            const node = e.element
            const parents = getParents(node)
            const matchedSelectors = {}
            Tools.each(selectorChangedData, (callbacks, selector) => {
              findMatchingNode(selector, parents).each((node) => {
                if (!currentSelectors[selector]) {
                  each$f(callbacks, (callback) => {
                    callback(true, {
                      node,
                      selector,
                      parents,
                    })
                  })
                  currentSelectors[selector] = callbacks
                }
                matchedSelectors[selector] = callbacks
              })
            })
            Tools.each(currentSelectors, (callbacks, selector) => {
              if (!matchedSelectors[selector]) {
                delete currentSelectors[selector]
                Tools.each(callbacks, (callback) => {
                  callback(false, {
                    node,
                    selector,
                    parents,
                  })
                })
              }
            })
          })
        }
        if (!selectorChangedData[selector]) {
          selectorChangedData[selector] = []
        }
        selectorChangedData[selector].push(callback)
        findMatchingNode(selector, getParents(editor.selection.getStart())).each(() => {
          currentSelectors[selector] = selectorChangedData[selector]
        })
        return {
          unbind: () => {
            deleteFromCallbackMap(selectorChangedData, selector, callback)
            deleteFromCallbackMap(currentSelectors, selector, callback)
          },
        }
      },
    }
  }

  const isAttachedToDom = (node) => {
    return (
      !!(node && node.ownerDocument) && contains(SugarElement.fromDom(node.ownerDocument), SugarElement.fromDom(node))
    )
  }
  const isValidRange = (rng) => {
    if (!rng) {
      return false
    } else {
      return isAttachedToDom(rng.startContainer) && isAttachedToDom(rng.endContainer)
    }
  }
  const EditorSelection = (dom, win, serializer, editor) => {
    let selectedRange
    let explicitRange
    const { selectorChangedWithUnbind } = SelectorChanged(dom, editor)
    const setCursorLocation = (node, offset) => {
      const rng = dom.createRng()
      if (isNonNullable(node) && isNonNullable(offset)) {
        rng.setStart(node, offset)
        rng.setEnd(node, offset)
        setRng(rng)
        collapse(false)
      } else {
        moveEndPoint(dom, rng, editor.getBody(), true)
        setRng(rng)
      }
    }
    const getContent = (args) => getContent$1(editor, args)
    const setContent = (content, args) => setContent$1(editor, content, args)
    const getStart$1 = (real) => getStart(editor.getBody(), getRng$1(), real)
    const getEnd = (real) => getEnd$1(editor.getBody(), getRng$1(), real)
    const getBookmark = (type, normalized) => bookmarkManager.getBookmark(type, normalized)
    const moveToBookmark = (bookmark) => bookmarkManager.moveToBookmark(bookmark)
    const select$1 = (node, content) => {
      select(dom, node, content).each(setRng)
      return node
    }
    const isCollapsed = () => {
      const rng = getRng$1(),
        sel = getSel()
      if (!rng || rng.item) {
        return false
      }
      if (rng.compareEndPoints) {
        return rng.compareEndPoints('StartToEnd', rng) === 0
      }
      return !sel || rng.collapsed
    }
    const collapse = (toStart) => {
      const rng = getRng$1()
      rng.collapse(!!toStart)
      setRng(rng)
    }
    const getSel = () => (win.getSelection ? win.getSelection() : win.document.selection)
    const getRng$1 = () => {
      let selection, rng, elm
      const tryCompareBoundaryPoints = (how, sourceRange, destinationRange) => {
        try {
          return sourceRange.compareBoundaryPoints(how, destinationRange)
        } catch (ex) {
          return -1
        }
      }
      const doc = win.document
      if (editor.bookmark !== undefined && hasFocus(editor) === false) {
        const bookmark = getRng(editor)
        if (bookmark.isSome()) {
          return bookmark.map((r) => processRanges(editor, [r])[0]).getOr(doc.createRange())
        }
      }
      try {
        if ((selection = getSel()) && !isRestrictedNode(selection.anchorNode)) {
          if (selection.rangeCount > 0) {
            rng = selection.getRangeAt(0)
          } else {
            rng = selection.createRange ? selection.createRange() : doc.createRange()
          }
          rng = processRanges(editor, [rng])[0]
        }
      } catch (ex) {}
      if (!rng) {
        rng = doc.createRange()
      }
      if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
        elm = dom.getRoot()
        rng.setStart(elm, 0)
        rng.setEnd(elm, 0)
      }
      if (selectedRange && explicitRange) {
        if (
          tryCompareBoundaryPoints(rng.START_TO_START, rng, selectedRange) === 0 &&
          tryCompareBoundaryPoints(rng.END_TO_END, rng, selectedRange) === 0
        ) {
          rng = explicitRange
        } else {
          selectedRange = null
          explicitRange = null
        }
      }
      return rng
    }
    const setRng = (rng, forward) => {
      let node
      if (!isValidRange(rng)) {
        return
      }
      const sel = getSel()
      const evt = editor.dispatch('SetSelectionRange', {
        range: rng,
        forward,
      })
      rng = evt.range
      if (sel) {
        explicitRange = rng
        try {
          sel.removeAllRanges()
          sel.addRange(rng)
        } catch (ex) {}
        if (forward === false && sel.extend) {
          sel.collapse(rng.endContainer, rng.endOffset)
          sel.extend(rng.startContainer, rng.startOffset)
        }
        selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null
      }
      if (!rng.collapsed && rng.startContainer === rng.endContainer && sel.setBaseAndExtent) {
        if (rng.endOffset - rng.startOffset < 2) {
          if (rng.startContainer.hasChildNodes()) {
            node = rng.startContainer.childNodes[rng.startOffset]
            if (node && node.tagName === 'IMG') {
              sel.setBaseAndExtent(rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset)
              if (sel.anchorNode !== rng.startContainer || sel.focusNode !== rng.endContainer) {
                sel.setBaseAndExtent(node, 0, node, 1)
              }
            }
          }
        }
      }
      editor.dispatch('AfterSetSelectionRange', {
        range: rng,
        forward,
      })
    }
    const setNode = (elm) => {
      setContent(dom.getOuterHTML(elm))
      return elm
    }
    const getNode$1 = () => getNode(editor.getBody(), getRng$1())
    const getSelectedBlocks$1 = (startElm, endElm) => getSelectedBlocks(dom, getRng$1(), startElm, endElm)
    const isForward = () => {
      const sel = getSel()
      const anchorNode = sel === null || sel === void 0 ? void 0 : sel.anchorNode
      const focusNode = sel === null || sel === void 0 ? void 0 : sel.focusNode
      if (!sel || !anchorNode || !focusNode || isRestrictedNode(anchorNode) || isRestrictedNode(focusNode)) {
        return true
      }
      const anchorRange = dom.createRng()
      const focusRange = dom.createRng()
      try {
        anchorRange.setStart(anchorNode, sel.anchorOffset)
        anchorRange.collapse(true)
        focusRange.setStart(focusNode, sel.focusOffset)
        focusRange.collapse(true)
      } catch (e) {
        return true
      }
      return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0
    }
    const normalize = () => {
      const rng = getRng$1()
      const sel = getSel()
      if (!hasMultipleRanges(sel) && hasAnyRanges(editor)) {
        const normRng = normalize$2(dom, rng)
        normRng.each((normRng) => {
          setRng(normRng, isForward())
        })
        return normRng.getOr(rng)
      }
      return rng
    }
    const selectorChanged = (selector, callback) => {
      selectorChangedWithUnbind(selector, callback)
      return exports
    }
    const getScrollContainer = () => {
      let scrollContainer
      let node = dom.getRoot()
      while (node && node.nodeName !== 'BODY') {
        if (node.scrollHeight > node.clientHeight) {
          scrollContainer = node
          break
        }
        node = node.parentNode
      }
      return scrollContainer
    }
    const scrollIntoView = (elm, alignToTop) => {
      if (isNonNullable(elm)) {
        scrollElementIntoView(editor, elm, alignToTop)
      } else {
        scrollRangeIntoView(editor, getRng$1(), alignToTop)
      }
    }
    const placeCaretAt = (clientX, clientY) => setRng(fromPoint(clientX, clientY, editor.getDoc()))
    const getBoundingClientRect = () => {
      const rng = getRng$1()
      return rng.collapsed ? CaretPosition.fromRangeStart(rng).getClientRects()[0] : rng.getBoundingClientRect()
    }
    const destroy = () => {
      win = selectedRange = explicitRange = null
      controlSelection.destroy()
    }
    const exports = {
      bookmarkManager: null,
      controlSelection: null,
      dom,
      win,
      serializer,
      editor,
      collapse,
      setCursorLocation,
      getContent,
      setContent,
      getBookmark,
      moveToBookmark,
      select: select$1,
      isCollapsed,
      isForward,
      setNode,
      getNode: getNode$1,
      getSel,
      setRng,
      getRng: getRng$1,
      getStart: getStart$1,
      getEnd,
      getSelectedBlocks: getSelectedBlocks$1,
      normalize,
      selectorChanged,
      selectorChangedWithUnbind,
      getScrollContainer,
      scrollIntoView,
      placeCaretAt,
      getBoundingClientRect,
      destroy,
    }
    const bookmarkManager = BookmarkManager(exports)
    const controlSelection = ControlSelection(exports, editor)
    exports.bookmarkManager = bookmarkManager
    exports.controlSelection = controlSelection
    return exports
  }

  const register$3 = (htmlParser, settings, dom) => {
    htmlParser.addAttributeFilter('data-mce-tabindex', (nodes, name) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        node.attr('tabindex', node.attr('data-mce-tabindex'))
        node.attr(name, null)
      }
    })
    htmlParser.addAttributeFilter('src,href,style', (nodes, name) => {
      const internalName = 'data-mce-' + name
      const urlConverter = settings.url_converter
      const urlConverterScope = settings.url_converter_scope
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        let value = node.attr(internalName)
        if (value !== undefined) {
          node.attr(name, value.length > 0 ? value : null)
          node.attr(internalName, null)
        } else {
          value = node.attr(name)
          if (name === 'style') {
            value = dom.serializeStyle(dom.parseStyle(value), node.name)
          } else if (urlConverter) {
            value = urlConverter.call(urlConverterScope, value, name, node.name)
          }
          node.attr(name, value.length > 0 ? value : null)
        }
      }
    })
    htmlParser.addAttributeFilter('class', (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        let value = node.attr('class')
        if (value) {
          value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '')
          node.attr('class', value.length > 0 ? value : null)
        }
      }
    })
    htmlParser.addAttributeFilter('data-mce-type', (nodes, name, args) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        if (node.attr('data-mce-type') === 'bookmark' && !args.cleanup) {
          const hasChildren = Optional.from(node.firstChild).exists((firstChild) => !isZwsp(firstChild.value))
          if (hasChildren) {
            node.unwrap()
          } else {
            node.remove()
          }
        }
      }
    })
    htmlParser.addNodeFilter('noscript', (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i].firstChild
        if (node) {
          node.value = Entities.decode(node.value)
        }
      }
    })
    htmlParser.addNodeFilter('script,style', (nodes, name) => {
      const trim = (value) => {
        return value
          .replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
          .replace(/^[\r\n]*|[\r\n]*$/g, '')
          .replace(
            /^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi,
            ''
          )
          .replace(
            /\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,
            ''
          )
      }
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        const value = node.firstChild ? node.firstChild.value : ''
        if (name === 'script') {
          const type = node.attr('type')
          if (type) {
            node.attr('type', type === 'mce-no/type' ? null : type.replace(/^mce\-/, ''))
          }
          if (settings.element_format === 'xhtml' && value.length > 0) {
            node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'
          }
        } else {
          if (settings.element_format === 'xhtml' && value.length > 0) {
            node.firstChild.value = '<!--\n' + trim(value) + '\n-->'
          }
        }
      }
    })
    htmlParser.addNodeFilter('#comment', (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        if (settings.preserve_cdata && node.value.indexOf('[CDATA[') === 0) {
          node.name = '#cdata'
          node.type = 4
          node.value = dom.decode(node.value.replace(/^\[CDATA\[|\]\]$/g, ''))
        } else if (node.value.indexOf('mce:protected ') === 0) {
          node.name = '#text'
          node.type = 3
          node.raw = true
          node.value = unescape(node.value).substr(14)
        }
      }
    })
    htmlParser.addNodeFilter('xml:namespace,input', (nodes, name) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        if (node.type === 7) {
          node.remove()
        } else if (node.type === 1) {
          if (name === 'input' && !node.attr('type')) {
            node.attr('type', 'text')
          }
        }
      }
    })
    htmlParser.addAttributeFilter('data-mce-type', (nodes) => {
      each$f(nodes, (node) => {
        if (node.attr('data-mce-type') === 'format-caret') {
          if (node.isEmpty(htmlParser.schema.getNonEmptyElements())) {
            node.remove()
          } else {
            node.unwrap()
          }
        }
      })
    })
    htmlParser.addAttributeFilter(
      'data-mce-src,data-mce-href,data-mce-style,' +
        'data-mce-selected,data-mce-expando,' +
        'data-mce-type,data-mce-resize,data-mce-placeholder',
      (nodes, name) => {
        let i = nodes.length
        while (i--) {
          nodes[i].attr(name, null)
        }
      }
    )
  }
  const trimTrailingBr = (rootNode) => {
    const isBr = (node) => {
      return node && node.name === 'br'
    }
    const brNode1 = rootNode.lastChild
    if (isBr(brNode1)) {
      const brNode2 = brNode1.prev
      if (isBr(brNode2)) {
        brNode1.remove()
        brNode2.remove()
      }
    }
  }

  const preProcess$1 = (editor, node, args) => {
    let oldDoc
    const dom = editor.dom
    let clonedNode = node.cloneNode(true)
    const impl = document.implementation
    if (impl.createHTMLDocument) {
      const doc = impl.createHTMLDocument('')
      Tools.each(clonedNode.nodeName === 'BODY' ? clonedNode.childNodes : [clonedNode], (node) => {
        doc.body.appendChild(doc.importNode(node, true))
      })
      if (clonedNode.nodeName !== 'BODY') {
        clonedNode = doc.body.firstChild
      } else {
        clonedNode = doc.body
      }
      oldDoc = dom.doc
      dom.doc = doc
    }
    firePreProcess(editor, {
      ...args,
      node: clonedNode,
    })
    if (oldDoc) {
      dom.doc = oldDoc
    }
    return clonedNode
  }
  const shouldFireEvent = (editor, args) => {
    return editor && editor.hasEventListeners('PreProcess') && !args.no_events
  }
  const process$1 = (editor, node, args) => {
    return shouldFireEvent(editor, args) ? preProcess$1(editor, node, args) : node
  }

  const addTempAttr = (htmlParser, tempAttrs, name) => {
    if (Tools.inArray(tempAttrs, name) === -1) {
      htmlParser.addAttributeFilter(name, (nodes, name) => {
        let i = nodes.length
        while (i--) {
          nodes[i].attr(name, null)
        }
      })
      tempAttrs.push(name)
    }
  }
  const postProcess = (editor, args, content) => {
    if (!args.no_events && editor) {
      const outArgs = firePostProcess(editor, {
        ...args,
        content,
      })
      return outArgs.content
    } else {
      return content
    }
  }
  const getHtmlFromNode = (dom, node, args) => {
    const html = trim$1(args.getInner ? node.innerHTML : dom.getOuterHTML(node))
    return args.selection || isWsPreserveElement(SugarElement.fromDom(node)) ? html : Tools.trim(html)
  }
  const parseHtml = (htmlParser, html, args) => {
    const parserArgs = args.selection
      ? {
          forced_root_block: false,
          ...args,
        }
      : args
    const rootNode = htmlParser.parse(html, parserArgs)
    trimTrailingBr(rootNode)
    return rootNode
  }
  const serializeNode = (settings, schema, node) => {
    const htmlSerializer = HtmlSerializer(settings, schema)
    return htmlSerializer.serialize(node)
  }
  const toHtml = (editor, settings, schema, rootNode, args) => {
    const content = serializeNode(settings, schema, rootNode)
    return postProcess(editor, args, content)
  }
  const DomSerializerImpl = (settings, editor) => {
    const tempAttrs = ['data-mce-selected']
    const dom = editor && editor.dom ? editor.dom : DOMUtils.DOM
    const schema = editor && editor.schema ? editor.schema : Schema(settings)
    settings.entity_encoding = settings.entity_encoding || 'named'
    settings.remove_trailing_brs = 'remove_trailing_brs' in settings ? settings.remove_trailing_brs : true
    const htmlParser = DomParser(settings, schema)
    register$3(htmlParser, settings, dom)
    const serialize = (node, parserArgs = {}) => {
      const args = {
        format: 'html',
        ...parserArgs,
      }
      const targetNode = process$1(editor, node, args)
      const html = getHtmlFromNode(dom, targetNode, args)
      const rootNode = parseHtml(htmlParser, html, args)
      return args.format === 'tree' ? rootNode : toHtml(editor, settings, schema, rootNode, args)
    }
    return {
      schema,
      addNodeFilter: htmlParser.addNodeFilter,
      addAttributeFilter: htmlParser.addAttributeFilter,
      serialize: serialize,
      addRules: schema.addValidElements,
      setRules: schema.setValidElements,
      addTempAttr: curry(addTempAttr, htmlParser, tempAttrs),
      getTempAttrs: constant(tempAttrs),
      getNodeFilters: htmlParser.getNodeFilters,
      getAttributeFilters: htmlParser.getAttributeFilters,
      removeNodeFilter: htmlParser.removeNodeFilter,
      removeAttributeFilter: htmlParser.removeAttributeFilter,
    }
  }

  const DomSerializer = (settings, editor) => {
    const domSerializer = DomSerializerImpl(settings, editor)
    return {
      schema: domSerializer.schema,
      addNodeFilter: domSerializer.addNodeFilter,
      addAttributeFilter: domSerializer.addAttributeFilter,
      serialize: domSerializer.serialize,
      addRules: domSerializer.addRules,
      setRules: domSerializer.setRules,
      addTempAttr: domSerializer.addTempAttr,
      getTempAttrs: domSerializer.getTempAttrs,
      getNodeFilters: domSerializer.getNodeFilters,
      getAttributeFilters: domSerializer.getAttributeFilters,
      removeNodeFilter: domSerializer.removeNodeFilter,
      removeAttributeFilter: domSerializer.removeAttributeFilter,
    }
  }

  const defaultFormat$1 = 'html'
  const setupArgs$1 = (args, format) => ({
    ...args,
    format,
    get: true,
    getInner: true,
  })
  const getContent = (editor, args = {}) => {
    const format = args.format ? args.format : defaultFormat$1
    const defaultedArgs = setupArgs$1(args, format)
    return preProcessGetContent(editor, defaultedArgs).fold(identity, (updatedArgs) => {
      const content = getContent$2(editor, updatedArgs)
      return postProcessGetContent(editor, content, updatedArgs)
    })
  }

  const defaultFormat = 'html'
  const setupArgs = (args, content) => ({
    format: defaultFormat,
    ...args,
    set: true,
    content,
  })
  const setContent = (editor, content, args = {}) => {
    const defaultedArgs = setupArgs(args, content)
    return preProcessSetContent(editor, defaultedArgs)
      .map((updatedArgs) => {
        const result = setContent$2(editor, updatedArgs.content, updatedArgs)
        postProcessSetContent(editor, result.html, updatedArgs)
        return result.content
      })
      .getOr(content)
  }

  const removedOptions = (
    'autoresize_on_init,content_editable_state,padd_empty_with_br,block_elements,' +
    'boolean_attributes,editor_deselector,editor_selector,elements,file_browser_callback_types,filepicker_validator_handler,' +
    'force_hex_style_colors,force_p_newlines,gecko_spellcheck,images_dataimg_filter,media_scripts,mode,move_caret_before_on_enter_elements,' +
    'non_empty_elements,self_closing_elements,short_ended_elements,special,spellchecker_select_languages,spellchecker_whitelist,' +
    'tab_focus,tabfocus_elements,table_responsive_width,text_block_elements,text_inline_elements,toolbar_drawer,types,validate,whitespace_elements,' +
    'paste_enable_default_filters,paste_filter_drop,paste_word_valid_elements,paste_retain_style_properties,paste_convert_word_fake_lists'
  ).split(',')
  const removedPlugins = 'bbcode,colorpicker,contextmenu,fullpage,legacyoutput,spellchecker,textcolor'.split(',')
  const getRemovedOptions = (options) => {
    const settingNames = filter$6(removedOptions, (setting) => has$2(options, setting))
    const forcedRootBlock = options.forced_root_block
    if (forcedRootBlock === false || forcedRootBlock === '') {
      settingNames.push('forced_root_block (false only)')
    }
    return sort(settingNames)
  }
  const getRemovedPlugins = (options) => {
    const plugins = Tools.makeMap(options.plugins, ' ')
    const hasPlugin = (plugin) => has$2(plugins, plugin)
    const pluginNames = filter$6(removedPlugins, hasPlugin)
    return sort(pluginNames)
  }
  const logRemovedWarnings = (rawOptions, normalizedOptions) => {
    const removedOptions = getRemovedOptions(rawOptions)
    const removedPlugins = getRemovedPlugins(normalizedOptions)
    const hasRemovedPlugins = removedPlugins.length > 0
    const hasRemovedOptions = removedOptions.length > 0
    const isLegacyMobileTheme = normalizedOptions.theme === 'mobile'
    if (hasRemovedPlugins || hasRemovedOptions || isLegacyMobileTheme) {
      const listJoiner = '\n- '
      const themesMessage = isLegacyMobileTheme ? `\n\nThemes:${listJoiner}mobile` : ''
      const pluginsMessage = hasRemovedPlugins ? `\n\nPlugins:${listJoiner}${removedPlugins.join(listJoiner)}` : ''
      const optionsMessage = hasRemovedOptions ? `\n\nOptions:${listJoiner}${removedOptions.join(listJoiner)}` : ''
      console.warn(
        'The following deprecated features are currently enabled and have been removed in TinyMCE 6.0. These features will no longer work and should be removed from the TinyMCE configuration. ' +
          'See https://www.tiny.cloud/docs/tinymce/6/migration-from-5x/ for more information.' +
          themesMessage +
          pluginsMessage +
          optionsMessage
      )
    }
  }
  const logWarnings = (rawOptions, normalizedOptions) => {
    logRemovedWarnings(rawOptions, normalizedOptions)
  }

  const DOM$8 = DOMUtils.DOM
  const restoreOriginalStyles = (editor) => {
    DOM$8.setStyle(editor.id, 'display', editor.orgDisplay)
  }
  const safeDestroy = (x) => Optional.from(x).each((x) => x.destroy())
  const clearDomReferences = (editor) => {
    editor.contentAreaContainer = editor.formElement = editor.container = editor.editorContainer = null
    editor.bodyElement = editor.contentDocument = editor.contentWindow = null
    editor.iframeElement = editor.targetElm = null
    if (editor.selection) {
      editor.selection = editor.selection.win = editor.selection.dom = editor.selection.dom.doc = null
    }
  }
  const restoreForm = (editor) => {
    const form = editor.formElement
    if (form) {
      if (form._mceOldSubmit) {
        form.submit = form._mceOldSubmit
        form._mceOldSubmit = null
      }
      DOM$8.unbind(form, 'submit reset', editor.formEventDelegate)
    }
  }
  const remove$1 = (editor) => {
    if (!editor.removed) {
      const { _selectionOverrides, editorUpload } = editor
      const body = editor.getBody()
      const element = editor.getElement()
      if (body) {
        editor.save({ is_removing: true })
      }
      editor.removed = true
      editor.unbindAllNativeEvents()
      if (editor.hasHiddenInput && element) {
        DOM$8.remove(element.nextSibling)
      }
      fireRemove(editor)
      editor.editorManager.remove(editor)
      if (!editor.inline && body) {
        restoreOriginalStyles(editor)
      }
      fireDetach(editor)
      DOM$8.remove(editor.getContainer())
      safeDestroy(_selectionOverrides)
      safeDestroy(editorUpload)
      editor.destroy()
    }
  }
  const destroy = (editor, automatic) => {
    const { selection, dom } = editor
    if (editor.destroyed) {
      return
    }
    if (!automatic && !editor.removed) {
      editor.remove()
      return
    }
    if (!automatic) {
      editor.editorManager.off('beforeunload', editor._beforeUnload)
      if (editor.theme && editor.theme.destroy) {
        editor.theme.destroy()
      }
      safeDestroy(selection)
      safeDestroy(dom)
    }
    restoreForm(editor)
    clearDomReferences(editor)
    editor.destroyed = true
  }

  const CreateIconManager = () => {
    const lookup = {}
    const add = (id, iconPack) => {
      lookup[id] = iconPack
    }
    const get = (id) => {
      if (lookup[id]) {
        return lookup[id]
      }
      return { icons: {} }
    }
    const has = (id) => has$2(lookup, id)
    return {
      add,
      get,
      has,
    }
  }
  const IconManager = CreateIconManager()

  const ModelManager = AddOnManager.ModelManager

  const getProp = (propName, elm) => {
    const rawElm = elm.dom
    return rawElm[propName]
  }
  const getComputedSizeProp = (propName, elm) => parseInt(get$7(elm, propName), 10)
  const getClientWidth = curry(getProp, 'clientWidth')
  const getClientHeight = curry(getProp, 'clientHeight')
  const getMarginTop = curry(getComputedSizeProp, 'margin-top')
  const getMarginLeft = curry(getComputedSizeProp, 'margin-left')
  const getBoundingClientRect = (elm) => elm.dom.getBoundingClientRect()
  const isInsideElementContentArea = (bodyElm, clientX, clientY) => {
    const clientWidth = getClientWidth(bodyElm)
    const clientHeight = getClientHeight(bodyElm)
    return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight
  }
  const transpose = (inline, elm, clientX, clientY) => {
    const clientRect = getBoundingClientRect(elm)
    const deltaX = inline ? clientRect.left + elm.dom.clientLeft + getMarginLeft(elm) : 0
    const deltaY = inline ? clientRect.top + elm.dom.clientTop + getMarginTop(elm) : 0
    const x = clientX - deltaX
    const y = clientY - deltaY
    return {
      x,
      y,
    }
  }
  const isXYInContentArea = (editor, clientX, clientY) => {
    const bodyElm = SugarElement.fromDom(editor.getBody())
    const targetElm = editor.inline ? bodyElm : documentElement(bodyElm)
    const transposedPoint = transpose(editor.inline, targetElm, clientX, clientY)
    return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y)
  }
  const fromDomSafe = (node) => Optional.from(node).map(SugarElement.fromDom)
  const isEditorAttachedToDom = (editor) => {
    const rawContainer = editor.inline ? editor.getBody() : editor.getContentAreaContainer()
    return fromDomSafe(rawContainer).map(inBody).getOr(false)
  }

  const NotificationManagerImpl = () => {
    const unimplemented = () => {
      throw new Error('Theme did not provide a NotificationManager implementation.')
    }
    return {
      open: unimplemented,
      close: unimplemented,
      getArgs: unimplemented,
    }
  }

  const NotificationManager = (editor) => {
    const notifications = []
    const getImplementation = () => {
      const theme = editor.theme
      return theme && theme.getNotificationManagerImpl ? theme.getNotificationManagerImpl() : NotificationManagerImpl()
    }
    const getTopNotification = () => {
      return Optional.from(notifications[0])
    }
    const isEqual = (a, b) => {
      return a.type === b.type && a.text === b.text && !a.progressBar && !a.timeout && !b.progressBar && !b.timeout
    }
    const reposition = () => {
      each$f(notifications, (notification) => {
        notification.reposition()
      })
    }
    const addNotification = (notification) => {
      notifications.push(notification)
    }
    const closeNotification = (notification) => {
      findIndex$2(notifications, (otherNotification) => {
        return otherNotification === notification
      }).each((index) => {
        notifications.splice(index, 1)
      })
    }
    const open = (spec, fireEvent = true) => {
      if (editor.removed || !isEditorAttachedToDom(editor)) {
        return
      }
      if (fireEvent) {
        editor.dispatch('BeforeOpenNotification', { notification: spec })
      }
      return find$2(notifications, (notification) => {
        return isEqual(getImplementation().getArgs(notification), spec)
      }).getOrThunk(() => {
        editor.editorManager.setActive(editor)
        const notification = getImplementation().open(spec, () => {
          closeNotification(notification)
          reposition()
          getTopNotification().fold(
            () => editor.focus(),
            (top) => focus$1(SugarElement.fromDom(top.getEl()))
          )
        })
        addNotification(notification)
        reposition()
        editor.dispatch('OpenNotification', { notification: { ...notification } })
        return notification
      })
    }
    const close = () => {
      getTopNotification().each((notification) => {
        getImplementation().close(notification)
        closeNotification(notification)
        reposition()
      })
    }
    const getNotifications = constant(notifications)
    const registerEvents = (editor) => {
      editor.on('SkinLoaded', () => {
        const serviceMessage = getServiceMessage(editor)
        if (serviceMessage) {
          open(
            {
              text: serviceMessage,
              type: 'warning',
              timeout: 0,
            },
            false
          )
        }
        reposition()
      })
      editor.on('show ResizeEditor ResizeWindow NodeChange', () => {
        requestAnimationFrame(reposition)
      })
      editor.on('remove', () => {
        each$f(notifications.slice(), (notification) => {
          getImplementation().close(notification)
        })
      })
    }
    registerEvents(editor)
    return {
      open,
      close,
      getNotifications,
    }
  }

  const PluginManager = AddOnManager.PluginManager

  const ThemeManager = AddOnManager.ThemeManager

  var WindowManagerImpl = () => {
    const unimplemented = () => {
      throw new Error('Theme did not provide a WindowManager implementation.')
    }
    return {
      open: unimplemented,
      openUrl: unimplemented,
      alert: unimplemented,
      confirm: unimplemented,
      close: unimplemented,
      getParams: unimplemented,
      setParams: unimplemented,
    }
  }

  const WindowManager = (editor) => {
    let dialogs = []
    const getImplementation = () => {
      const theme = editor.theme
      return theme && theme.getWindowManagerImpl ? theme.getWindowManagerImpl() : WindowManagerImpl()
    }
    const funcBind = (scope, f) => {
      return (...args) => {
        return f ? f.apply(scope, args) : undefined
      }
    }
    const fireOpenEvent = (dialog) => {
      editor.dispatch('OpenWindow', { dialog })
    }
    const fireCloseEvent = (dialog) => {
      editor.dispatch('CloseWindow', { dialog })
    }
    const addDialog = (dialog) => {
      dialogs.push(dialog)
      fireOpenEvent(dialog)
    }
    const closeDialog = (dialog) => {
      fireCloseEvent(dialog)
      dialogs = filter$6(dialogs, (otherDialog) => {
        return otherDialog !== dialog
      })
      if (dialogs.length === 0) {
        editor.focus()
      }
    }
    const getTopDialog = () => {
      return Optional.from(dialogs[dialogs.length - 1])
    }
    const storeSelectionAndOpenDialog = (openDialog) => {
      editor.editorManager.setActive(editor)
      store(editor)
      editor.ui.show()
      const dialog = openDialog()
      addDialog(dialog)
      return dialog
    }
    const open = (args, params) => {
      return storeSelectionAndOpenDialog(() => getImplementation().open(args, params, closeDialog))
    }
    const openUrl = (args) => {
      return storeSelectionAndOpenDialog(() => getImplementation().openUrl(args, closeDialog))
    }
    const alert = (message, callback, scope) => {
      const windowManagerImpl = getImplementation()
      windowManagerImpl.alert(message, funcBind(scope ? scope : windowManagerImpl, callback))
    }
    const confirm = (message, callback, scope) => {
      const windowManagerImpl = getImplementation()
      windowManagerImpl.confirm(message, funcBind(scope ? scope : windowManagerImpl, callback))
    }
    const close = () => {
      getTopDialog().each((dialog) => {
        getImplementation().close(dialog)
        closeDialog(dialog)
      })
    }
    editor.on('remove', () => {
      each$f(dialogs, (dialog) => {
        getImplementation().close(dialog)
      })
    })
    return {
      open,
      openUrl,
      alert,
      confirm,
      close,
    }
  }

  const displayNotification = (editor, message) => {
    editor.notificationManager.open({
      type: 'error',
      text: message,
    })
  }
  const displayError = (editor, message) => {
    if (editor._skinLoaded) {
      displayNotification(editor, message)
    } else {
      editor.on('SkinLoaded', () => {
        displayNotification(editor, message)
      })
    }
  }
  const uploadError = (editor, message) => {
    displayError(editor, I18n.translate(['Failed to upload image: {0}', message]))
  }
  const logError = (editor, errorType, msg) => {
    fireError(editor, errorType, { message: msg })
    console.error(msg)
  }
  const createLoadError = (type, url, name) =>
    name ? `Failed to load ${type}: ${name} from url ${url}` : `Failed to load ${type} url: ${url}`
  const pluginLoadError = (editor, url, name) => {
    logError(editor, 'PluginLoadError', createLoadError('plugin', url, name))
  }
  const iconsLoadError = (editor, url, name) => {
    logError(editor, 'IconsLoadError', createLoadError('icons', url, name))
  }
  const languageLoadError = (editor, url, name) => {
    logError(editor, 'LanguageLoadError', createLoadError('language', url, name))
  }
  const themeLoadError = (editor, url, name) => {
    logError(editor, 'ThemeLoadError', createLoadError('theme', url, name))
  }
  const modelLoadError = (editor, url, name) => {
    logError(editor, 'ModelLoadError', createLoadError('model', url, name))
  }
  const pluginInitError = (editor, name, err) => {
    const message = I18n.translate(['Failed to initialize plugin: {0}', name])
    fireError(editor, 'PluginLoadError', { message })
    initError(message, err)
    displayError(editor, message)
  }
  const initError = (message, ...x) => {
    const console = window.console
    if (console) {
      if (console.error) {
        console.error(message, ...x)
      } else {
        console.log(message, ...x)
      }
    }
  }

  const isContentCssSkinName = (url) => /^[a-z0-9\-]+$/i.test(url)
  const getContentCssUrls = (editor) => {
    return transformToUrls(editor, getContentCss(editor))
  }
  const getFontCssUrls = (editor) => {
    return transformToUrls(editor, getFontCss(editor))
  }
  const transformToUrls = (editor, cssLinks) => {
    const skinUrl = editor.editorManager.baseURL + '/skins/content'
    const suffix = editor.editorManager.suffix
    const contentCssFile = `content${suffix}.css`
    const inline = editor.inline === true
    return map$3(cssLinks, (url) => {
      if (isContentCssSkinName(url) && !inline) {
        return `${skinUrl}/${url}/${contentCssFile}`
      } else {
        return editor.documentBaseURI.toAbsolute(url)
      }
    })
  }
  const appendContentCssFromSettings = (editor) => {
    editor.contentCSS = editor.contentCSS.concat(getContentCssUrls(editor), getFontCssUrls(editor))
  }

  const filter$1 = always
  const bind$1 = (element, event, handler) => bind$2(element, event, filter$1, handler)

  const getAllImages = (elm) => {
    return elm ? from(elm.getElementsByTagName('img')) : []
  }
  const ImageScanner = (uploadStatus, blobCache) => {
    const cachedPromises = {}
    const findAll = (elm, predicate = always) => {
      const images = filter$6(getAllImages(elm), (img) => {
        const src = img.src
        if (img.hasAttribute('data-mce-bogus')) {
          return false
        }
        if (img.hasAttribute('data-mce-placeholder')) {
          return false
        }
        if (!src || src === Env.transparentSrc) {
          return false
        }
        if (startsWith(src, 'blob:')) {
          return !uploadStatus.isUploaded(src) && predicate(img)
        }
        if (startsWith(src, 'data:')) {
          return predicate(img)
        }
        return false
      })
      const promises = map$3(images, (img) => {
        const imageSrc = img.src
        if (has$2(cachedPromises, imageSrc)) {
          return cachedPromises[imageSrc].then((imageInfo) => {
            if (isString(imageInfo)) {
              return imageInfo
            } else {
              return {
                image: img,
                blobInfo: imageInfo.blobInfo,
              }
            }
          })
        } else {
          const newPromise = imageToBlobInfo(blobCache, imageSrc)
            .then((blobInfo) => {
              delete cachedPromises[imageSrc]
              return {
                image: img,
                blobInfo,
              }
            })
            .catch((error) => {
              delete cachedPromises[imageSrc]
              return error
            })
          cachedPromises[imageSrc] = newPromise
          return newPromise
        }
      })
      return Promise.all(promises)
    }
    return { findAll }
  }

  const UploadStatus = () => {
    const PENDING = 1,
      UPLOADED = 2
    let blobUriStatuses = {}
    const createStatus = (status, resultUri) => {
      return {
        status,
        resultUri,
      }
    }
    const hasBlobUri = (blobUri) => {
      return blobUri in blobUriStatuses
    }
    const getResultUri = (blobUri) => {
      const result = blobUriStatuses[blobUri]
      return result ? result.resultUri : null
    }
    const isPending = (blobUri) => {
      return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false
    }
    const isUploaded = (blobUri) => {
      return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false
    }
    const markPending = (blobUri) => {
      blobUriStatuses[blobUri] = createStatus(PENDING, null)
    }
    const markUploaded = (blobUri, resultUri) => {
      blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri)
    }
    const removeFailed = (blobUri) => {
      delete blobUriStatuses[blobUri]
    }
    const destroy = () => {
      blobUriStatuses = {}
    }
    return {
      hasBlobUri,
      getResultUri,
      isPending,
      isUploaded,
      markPending,
      markUploaded,
      removeFailed,
      destroy,
    }
  }

  let count = 0
  const seed = () => {
    const rnd = () => {
      return Math.round(Math.random() * 4294967295).toString(36)
    }
    const now = new Date().getTime()
    return 's' + now.toString(36) + rnd() + rnd() + rnd()
  }
  const uuid = (prefix) => {
    return prefix + count++ + seed()
  }

  const BlobCache = () => {
    let cache = []
    const mimeToExt = (mime) => {
      const mimes = {
        'image/jpeg': 'jpg',
        'image/jpg': 'jpg',
        'image/gif': 'gif',
        'image/png': 'png',
        'image/apng': 'apng',
        'image/avif': 'avif',
        'image/svg+xml': 'svg',
        'image/webp': 'webp',
        'image/bmp': 'bmp',
        'image/tiff': 'tiff',
      }
      return mimes[mime.toLowerCase()] || 'dat'
    }
    const create = (o, blob, base64, name, filename) => {
      if (isString(o)) {
        const id = o
        return toBlobInfo({
          id,
          name,
          filename,
          blob,
          base64,
        })
      } else if (isObject(o)) {
        return toBlobInfo(o)
      } else {
        throw new Error('Unknown input type')
      }
    }
    const toBlobInfo = (o) => {
      if (!o.blob || !o.base64) {
        throw new Error('blob and base64 representations of the image are required for BlobInfo to be created')
      }
      const id = o.id || uuid('blobid')
      const name = o.name || id
      const blob = o.blob
      return {
        id: constant(id),
        name: constant(name),
        filename: constant(o.filename || name + '.' + mimeToExt(blob.type)),
        blob: constant(blob),
        base64: constant(o.base64),
        blobUri: constant(o.blobUri || URL.createObjectURL(blob)),
        uri: constant(o.uri),
      }
    }
    const add = (blobInfo) => {
      if (!get(blobInfo.id())) {
        cache.push(blobInfo)
      }
    }
    const findFirst = (predicate) => find$2(cache, predicate).getOrUndefined()
    const get = (id) => findFirst((cachedBlobInfo) => cachedBlobInfo.id() === id)
    const getByUri = (blobUri) => findFirst((blobInfo) => blobInfo.blobUri() === blobUri)
    const getByData = (base64, type) =>
      findFirst((blobInfo) => blobInfo.base64() === base64 && blobInfo.blob().type === type)
    const removeByUri = (blobUri) => {
      cache = filter$6(cache, (blobInfo) => {
        if (blobInfo.blobUri() === blobUri) {
          URL.revokeObjectURL(blobInfo.blobUri())
          return false
        }
        return true
      })
    }
    const destroy = () => {
      each$f(cache, (cachedBlobInfo) => {
        URL.revokeObjectURL(cachedBlobInfo.blobUri())
      })
      cache = []
    }
    return {
      create,
      add,
      get,
      getByUri,
      getByData,
      findFirst,
      removeByUri,
      destroy,
    }
  }

  const Uploader = (uploadStatus, settings) => {
    const pendingPromises = {}
    const pathJoin = (path1, path2) => {
      if (path1) {
        return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '')
      }
      return path2
    }
    const defaultHandler = (blobInfo, progress) =>
      new Promise((success, failure) => {
        const xhr = new XMLHttpRequest()
        xhr.open('POST', settings.url)
        xhr.withCredentials = settings.credentials
        xhr.upload.onprogress = (e) => {
          progress((e.loaded / e.total) * 100)
        }
        xhr.onerror = () => {
          failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status)
        }
        xhr.onload = () => {
          if (xhr.status < 200 || xhr.status >= 300) {
            failure('HTTP Error: ' + xhr.status)
            return
          }
          const json = JSON.parse(xhr.responseText)
          if (!json || !isString(json.location)) {
            failure('Invalid JSON: ' + xhr.responseText)
            return
          }
          success(pathJoin(settings.basePath, json.location))
        }
        const formData = new FormData()
        formData.append('file', blobInfo.blob(), blobInfo.filename())
        xhr.send(formData)
      })
    const noUpload = () =>
      new Promise((resolve) => {
        resolve([])
      })
    const handlerSuccess = (blobInfo, url) => ({
      url,
      blobInfo,
      status: true,
    })
    const handlerFailure = (blobInfo, error) => ({
      url: '',
      blobInfo,
      status: false,
      error,
    })
    const resolvePending = (blobUri, result) => {
      Tools.each(pendingPromises[blobUri], (resolve) => {
        resolve(result)
      })
      delete pendingPromises[blobUri]
    }
    const uploadBlobInfo = (blobInfo, handler, openNotification) => {
      uploadStatus.markPending(blobInfo.blobUri())
      return new Promise((resolve) => {
        let notification
        let progress
        try {
          const closeNotification = () => {
            if (notification) {
              notification.close()
              progress = noop
            }
          }
          const success = (url) => {
            closeNotification()
            uploadStatus.markUploaded(blobInfo.blobUri(), url)
            resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url))
            resolve(handlerSuccess(blobInfo, url))
          }
          const failure = (error) => {
            closeNotification()
            uploadStatus.removeFailed(blobInfo.blobUri())
            resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error))
            resolve(handlerFailure(blobInfo, error))
          }
          progress = (percent) => {
            if (percent < 0 || percent > 100) {
              return
            }
            Optional.from(notification)
              .orThunk(() => Optional.from(openNotification).map(apply$1))
              .each((n) => {
                notification = n
                n.progressBar.value(percent)
              })
          }
          handler(blobInfo, progress).then(success, (err) => {
            failure(isString(err) ? { message: err } : err)
          })
        } catch (ex) {
          resolve(handlerFailure(blobInfo, ex))
        }
      })
    }
    const isDefaultHandler = (handler) => handler === defaultHandler
    const pendingUploadBlobInfo = (blobInfo) => {
      const blobUri = blobInfo.blobUri()
      return new Promise((resolve) => {
        pendingPromises[blobUri] = pendingPromises[blobUri] || []
        pendingPromises[blobUri].push(resolve)
      })
    }
    const uploadBlobs = (blobInfos, openNotification) => {
      blobInfos = Tools.grep(blobInfos, (blobInfo) => !uploadStatus.isUploaded(blobInfo.blobUri()))
      return Promise.all(
        Tools.map(blobInfos, (blobInfo) =>
          uploadStatus.isPending(blobInfo.blobUri())
            ? pendingUploadBlobInfo(blobInfo)
            : uploadBlobInfo(blobInfo, settings.handler, openNotification)
        )
      )
    }
    const upload = (blobInfos, openNotification) =>
      !settings.url && isDefaultHandler(settings.handler) ? noUpload() : uploadBlobs(blobInfos, openNotification)
    if (isFunction(settings.handler) === false) {
      settings.handler = defaultHandler
    }
    return { upload }
  }

  const openNotification = (editor) => () =>
    editor.notificationManager.open({
      text: editor.translate('Image uploading...'),
      type: 'info',
      timeout: -1,
      progressBar: true,
    })
  const createUploader = (editor, uploadStatus) =>
    Uploader(uploadStatus, {
      url: getImageUploadUrl(editor),
      basePath: getImageUploadBasePath(editor),
      credentials: getImagesUploadCredentials(editor),
      handler: getImagesUploadHandler(editor),
    })
  const ImageUploader = (editor) => {
    const uploadStatus = UploadStatus()
    const uploader = createUploader(editor, uploadStatus)
    return {
      upload: (blobInfos, showNotification = true) =>
        uploader.upload(blobInfos, showNotification ? openNotification(editor) : undefined),
    }
  }

  const EditorUpload = (editor) => {
    const blobCache = BlobCache()
    let uploader, imageScanner
    const uploadStatus = UploadStatus()
    const urlFilters = []
    const aliveGuard = (callback) => {
      return (result) => {
        if (editor.selection) {
          return callback(result)
        }
        return []
      }
    }
    const cacheInvalidator = (url) => url + (url.indexOf('?') === -1 ? '?' : '&') + new Date().getTime()
    const replaceString = (content, search, replace) => {
      let index = 0
      do {
        index = content.indexOf(search, index)
        if (index !== -1) {
          content = content.substring(0, index) + replace + content.substr(index + search.length)
          index += replace.length - search.length + 1
        }
      } while (index !== -1)
      return content
    }
    const replaceImageUrl = (content, targetUrl, replacementUrl) => {
      const replacementString = `src="${replacementUrl}"${
        replacementUrl === Env.transparentSrc ? ' data-mce-placeholder="1"' : ''
      }`
      content = replaceString(content, `src="${targetUrl}"`, replacementString)
      content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"')
      return content
    }
    const replaceUrlInUndoStack = (targetUrl, replacementUrl) => {
      each$f(editor.undoManager.data, (level) => {
        if (level.type === 'fragmented') {
          level.fragments = map$3(level.fragments, (fragment) => replaceImageUrl(fragment, targetUrl, replacementUrl))
        } else {
          level.content = replaceImageUrl(level.content, targetUrl, replacementUrl)
        }
      })
    }
    const replaceImageUriInView = (image, resultUri) => {
      const src = editor.convertURL(resultUri, 'src')
      replaceUrlInUndoStack(image.src, resultUri)
      setAll$1(SugarElement.fromDom(image), {
        'src': shouldReuseFileName(editor) ? cacheInvalidator(resultUri) : resultUri,
        'data-mce-src': src,
      })
    }
    const uploadImages = () => {
      if (!uploader) {
        uploader = createUploader(editor, uploadStatus)
      }
      return scanForImages().then(
        aliveGuard((imageInfos) => {
          const blobInfos = map$3(imageInfos, (imageInfo) => imageInfo.blobInfo)
          return uploader.upload(blobInfos, openNotification(editor)).then(
            aliveGuard((result) => {
              const imagesToRemove = []
              let shouldDispatchChange = false
              const filteredResult = map$3(result, (uploadInfo, index) => {
                const blobInfo = imageInfos[index].blobInfo
                const image = imageInfos[index].image
                let removed = false
                if (uploadInfo.status && shouldReplaceBlobUris(editor)) {
                  if (uploadInfo.url && !contains$1(image.src, uploadInfo.url)) {
                    shouldDispatchChange = true
                  }
                  blobCache.removeByUri(image.src)
                  if (isRtc(editor));
                  else {
                    replaceImageUriInView(image, uploadInfo.url)
                  }
                } else if (uploadInfo.error) {
                  if (uploadInfo.error.remove) {
                    replaceUrlInUndoStack(image.getAttribute('src'), Env.transparentSrc)
                    imagesToRemove.push(image)
                    removed = true
                  }
                  uploadError(editor, uploadInfo.error.message)
                }
                return {
                  element: image,
                  status: uploadInfo.status,
                  uploadUri: uploadInfo.url,
                  blobInfo,
                  removed,
                }
              })
              if (imagesToRemove.length > 0 && !isRtc(editor)) {
                editor.undoManager.transact(() => {
                  each$f(imagesToRemove, (element) => {
                    editor.dom.remove(element)
                    blobCache.removeByUri(element.src)
                  })
                })
              } else if (shouldDispatchChange) {
                editor.undoManager.dispatchChange()
              }
              return filteredResult
            })
          )
        })
      )
    }
    const uploadImagesAuto = () => (isAutomaticUploadsEnabled(editor) ? uploadImages() : Promise.resolve([]))
    const isValidDataUriImage = (imgElm) => forall(urlFilters, (filter) => filter(imgElm))
    const addFilter = (filter) => {
      urlFilters.push(filter)
    }
    const scanForImages = () => {
      if (!imageScanner) {
        imageScanner = ImageScanner(uploadStatus, blobCache)
      }
      return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(
        aliveGuard((result) => {
          const filteredResult = filter$6(result, (resultItem) => {
            if (isString(resultItem)) {
              displayError(editor, resultItem)
              return false
            } else {
              return true
            }
          })
          if (isRtc(editor));
          else {
            each$f(filteredResult, (resultItem) => {
              replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri())
              resultItem.image.src = resultItem.blobInfo.blobUri()
              resultItem.image.removeAttribute('data-mce-src')
            })
          }
          return filteredResult
        })
      )
    }
    const destroy = () => {
      blobCache.destroy()
      uploadStatus.destroy()
      imageScanner = uploader = null
    }
    const replaceBlobUris = (content) => {
      return content.replace(/src="(blob:[^"]+)"/g, (match, blobUri) => {
        const resultUri = uploadStatus.getResultUri(blobUri)
        if (resultUri) {
          return 'src="' + resultUri + '"'
        }
        let blobInfo = blobCache.getByUri(blobUri)
        if (!blobInfo) {
          blobInfo = foldl(
            editor.editorManager.get(),
            (result, editor) => {
              return result || (editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri))
            },
            null
          )
        }
        if (blobInfo) {
          const blob = blobInfo.blob()
          return 'src="data:' + blob.type + ';base64,' + blobInfo.base64() + '"'
        }
        return match
      })
    }
    editor.on('SetContent', () => {
      if (isAutomaticUploadsEnabled(editor)) {
        uploadImagesAuto()
      } else {
        scanForImages()
      }
    })
    editor.on('RawSaveContent', (e) => {
      e.content = replaceBlobUris(e.content)
    })
    editor.on('GetContent', (e) => {
      if (e.source_view || e.format === 'raw' || e.format === 'tree') {
        return
      }
      e.content = replaceBlobUris(e.content)
    })
    editor.on('PostRender', () => {
      editor.parser.addNodeFilter('img', (images) => {
        each$f(images, (img) => {
          const src = img.attr('src')
          if (blobCache.getByUri(src)) {
            return
          }
          const resultUri = uploadStatus.getResultUri(src)
          if (resultUri) {
            img.attr('src', resultUri)
          }
        })
      })
    })
    return {
      blobCache,
      addFilter,
      uploadImages,
      uploadImagesAuto,
      scanForImages,
      destroy,
    }
  }

  const get$1 = (editor) => {
    const dom = editor.dom
    const schemaType = editor.schema.type
    const formats = {
      valigntop: [
        {
          selector: 'td,th',
          styles: { verticalAlign: 'top' },
        },
      ],
      valignmiddle: [
        {
          selector: 'td,th',
          styles: { verticalAlign: 'middle' },
        },
      ],
      valignbottom: [
        {
          selector: 'td,th',
          styles: { verticalAlign: 'bottom' },
        },
      ],
      alignleft: [
        {
          selector: 'figure.image',
          collapsed: false,
          classes: 'align-left',
          ceFalseOverride: true,
          preview: 'font-family font-size',
        },
        {
          selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
          styles: { textAlign: 'left' },
          inherit: false,
          preview: false,
        },
        {
          selector: 'img,audio,video',
          collapsed: false,
          styles: { float: 'left' },
          preview: 'font-family font-size',
        },
        {
          selector: 'table',
          collapsed: false,
          styles: {
            marginLeft: '0px',
            marginRight: 'auto',
          },
          onformat: (table) => {
            dom.setStyle(table, 'float', null)
          },
          preview: 'font-family font-size',
        },
      ],
      aligncenter: [
        {
          selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
          styles: { textAlign: 'center' },
          inherit: false,
          preview: 'font-family font-size',
        },
        {
          selector: 'figure.image',
          collapsed: false,
          classes: 'align-center',
          ceFalseOverride: true,
          preview: 'font-family font-size',
        },
        {
          selector: 'img,audio,video',
          collapsed: false,
          styles: {
            display: 'block',
            marginLeft: 'auto',
            marginRight: 'auto',
          },
          preview: false,
        },
        {
          selector: 'table',
          collapsed: false,
          styles: {
            marginLeft: 'auto',
            marginRight: 'auto',
          },
          preview: 'font-family font-size',
        },
      ],
      alignright: [
        {
          selector: 'figure.image',
          collapsed: false,
          classes: 'align-right',
          ceFalseOverride: true,
          preview: 'font-family font-size',
        },
        {
          selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
          styles: { textAlign: 'right' },
          inherit: false,
          preview: 'font-family font-size',
        },
        {
          selector: 'img,audio,video',
          collapsed: false,
          styles: { float: 'right' },
          preview: 'font-family font-size',
        },
        {
          selector: 'table',
          collapsed: false,
          styles: {
            marginRight: '0px',
            marginLeft: 'auto',
          },
          onformat: (table) => {
            dom.setStyle(table, 'float', null)
          },
          preview: 'font-family font-size',
        },
      ],
      alignjustify: [
        {
          selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
          styles: { textAlign: 'justify' },
          inherit: false,
          preview: 'font-family font-size',
        },
      ],
      bold: [
        {
          inline: 'strong',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        },
        {
          inline: 'span',
          styles: { fontWeight: 'bold' },
        },
        {
          inline: 'b',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        },
      ],
      italic: [
        {
          inline: 'em',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        },
        {
          inline: 'span',
          styles: { fontStyle: 'italic' },
        },
        {
          inline: 'i',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        },
      ],
      underline: [
        {
          inline: 'span',
          styles: { textDecoration: 'underline' },
          exact: true,
        },
        {
          inline: 'u',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        },
      ],
      strikethrough: (() => {
        const span = {
          inline: 'span',
          styles: { textDecoration: 'line-through' },
          exact: true,
        }
        const strike = {
          inline: 'strike',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        }
        const s = {
          inline: 's',
          remove: 'all',
          preserve_attributes: ['class', 'style'],
        }
        return schemaType !== 'html4' ? [s, span, strike] : [span, s, strike]
      })(),
      forecolor: {
        inline: 'span',
        styles: { color: '%value' },
        links: true,
        remove_similar: true,
        clear_child_styles: true,
      },
      hilitecolor: {
        inline: 'span',
        styles: { backgroundColor: '%value' },
        links: true,
        remove_similar: true,
        clear_child_styles: true,
      },
      fontname: {
        inline: 'span',
        toggle: false,
        styles: { fontFamily: '%value' },
        clear_child_styles: true,
      },
      fontsize: {
        inline: 'span',
        toggle: false,
        styles: { fontSize: '%value' },
        clear_child_styles: true,
      },
      lineheight: {
        selector: 'h1,h2,h3,h4,h5,h6,p,li,td,th,div',
        styles: { lineHeight: '%value' },
      },
      fontsize_class: {
        inline: 'span',
        attributes: { class: '%value' },
      },
      blockquote: {
        block: 'blockquote',
        wrapper: true,
        remove: 'all',
      },
      subscript: { inline: 'sub' },
      superscript: { inline: 'sup' },
      code: { inline: 'code' },
      link: {
        inline: 'a',
        selector: 'a',
        remove: 'all',
        split: true,
        deep: true,
        onmatch: (node, _fmt, _itemName) => {
          return isElement$6(node) && node.hasAttribute('href')
        },
        onformat: (elm, _fmt, vars) => {
          Tools.each(vars, (value, key) => {
            dom.setAttrib(elm, key, value)
          })
        },
      },
      lang: {
        inline: 'span',
        clear_child_styles: true,
        remove_similar: true,
        attributes: {
          'lang': '%value',
          'data-mce-lang': (vars) => {
            var _a
            return (_a = vars === null || vars === void 0 ? void 0 : vars.customValue) !== null && _a !== void 0
              ? _a
              : null
          },
        },
      },
      removeformat: [
        {
          selector: 'b,strong,em,i,font,u,strike,s,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins,small',
          remove: 'all',
          split: true,
          expand: false,
          block_expand: true,
          deep: true,
        },
        {
          selector: 'span',
          attributes: ['style', 'class'],
          remove: 'empty',
          split: true,
          expand: false,
          deep: true,
        },
        {
          selector: '*',
          attributes: ['style', 'class'],
          split: false,
          expand: false,
          deep: true,
        },
      ],
    }
    Tools.each('p h1 h2 h3 h4 h5 h6 div address pre dt dd samp'.split(/\s/), (name) => {
      formats[name] = {
        block: name,
        remove: 'all',
      }
    })
    return formats
  }

  const genericBase = {
    remove_similar: true,
    inherit: false,
  }
  const cellBase = {
    selector: 'td,th',
    ...genericBase,
  }
  const cellFormats = {
    tablecellbackgroundcolor: {
      styles: { backgroundColor: '%value' },
      ...cellBase,
    },
    tablecellverticalalign: {
      styles: { 'vertical-align': '%value' },
      ...cellBase,
    },
    tablecellbordercolor: {
      styles: { borderColor: '%value' },
      ...cellBase,
    },
    tablecellclass: {
      classes: ['%value'],
      ...cellBase,
    },
    tableclass: {
      selector: 'table',
      classes: ['%value'],
      ...genericBase,
    },
    tablecellborderstyle: {
      styles: { borderStyle: '%value' },
      ...cellBase,
    },
    tablecellborderwidth: {
      styles: { borderWidth: '%value' },
      ...cellBase,
    },
  }
  const get = constant(cellFormats)

  const FormatRegistry = (editor) => {
    const formats = {}
    const get$2 = (name) => (isNonNullable(name) ? formats[name] : formats)
    const has = (name) => has$2(formats, name)
    const register = (name, format) => {
      if (name) {
        if (!isString(name)) {
          each$e(name, (format, name) => {
            register(name, format)
          })
        } else {
          if (!isArray$1(format)) {
            format = [format]
          }
          each$f(format, (format) => {
            if (isUndefined(format.deep)) {
              format.deep = !isSelectorFormat(format)
            }
            if (isUndefined(format.split)) {
              format.split = !isSelectorFormat(format) || isInlineFormat(format)
            }
            if (isUndefined(format.remove) && isSelectorFormat(format) && !isInlineFormat(format)) {
              format.remove = 'none'
            }
            if (isSelectorFormat(format) && isInlineFormat(format)) {
              format.mixed = true
              format.block_expand = true
            }
            if (isString(format.classes)) {
              format.classes = format.classes.split(/\s+/)
            }
          })
          formats[name] = format
        }
      }
    }
    const unregister = (name) => {
      if (name && formats[name]) {
        delete formats[name]
      }
      return formats
    }
    register(get$1(editor))
    register(get())
    register(getFormats(editor))
    return {
      get: get$2,
      has,
      register,
      unregister,
    }
  }

  const each$3 = Tools.each
  const dom = DOMUtils.DOM
  const parsedSelectorToHtml = (ancestry, editor) => {
    let elm, item, fragment
    const schema = (editor && editor.schema) || Schema({})
    const decorate = (elm, item) => {
      if (item.classes.length) {
        dom.addClass(elm, item.classes.join(' '))
      }
      dom.setAttribs(elm, item.attrs)
    }
    const createElement = (sItem) => {
      item =
        typeof sItem === 'string'
          ? {
              name: sItem,
              classes: [],
              attrs: {},
            }
          : sItem
      const elm = dom.create(item.name)
      decorate(elm, item)
      return elm
    }
    const getRequiredParent = (elm, candidate) => {
      const name = typeof elm !== 'string' ? elm.nodeName.toLowerCase() : elm
      const elmRule = schema.getElementRule(name)
      const parentsRequired = elmRule && elmRule.parentsRequired
      if (parentsRequired && parentsRequired.length) {
        return candidate && Tools.inArray(parentsRequired, candidate) !== -1 ? candidate : parentsRequired[0]
      } else {
        return false
      }
    }
    const wrapInHtml = (elm, ancestry, siblings) => {
      let parent, parentCandidate
      const ancestor = ancestry.length > 0 && ancestry[0]
      const ancestorName = ancestor && ancestor.name
      const parentRequired = getRequiredParent(elm, ancestorName)
      if (parentRequired) {
        if (ancestorName === parentRequired) {
          parentCandidate = ancestry[0]
          ancestry = ancestry.slice(1)
        } else {
          parentCandidate = parentRequired
        }
      } else if (ancestor) {
        parentCandidate = ancestry[0]
        ancestry = ancestry.slice(1)
      } else if (!siblings) {
        return elm
      }
      if (parentCandidate) {
        parent = createElement(parentCandidate)
        parent.appendChild(elm)
      }
      if (siblings) {
        if (!parent) {
          parent = dom.create('div')
          parent.appendChild(elm)
        }
        Tools.each(siblings, (sibling) => {
          const siblingElm = createElement(sibling)
          parent.insertBefore(siblingElm, elm)
        })
      }
      return wrapInHtml(parent, ancestry, parentCandidate && parentCandidate.siblings)
    }
    if (ancestry && ancestry.length) {
      item = ancestry[0]
      elm = createElement(item)
      fragment = dom.create('div')
      fragment.appendChild(wrapInHtml(elm, ancestry.slice(1), item.siblings))
      return fragment
    } else {
      return ''
    }
  }
  const parseSelectorItem = (item) => {
    let tagName
    const obj = {
      classes: [],
      attrs: {},
    }
    item = obj.selector = Tools.trim(item)
    if (item !== '*') {
      tagName = item.replace(/(?:([#\.]|::?)([\w\-]+)|(\[)([^\]]+)\]?)/g, ($0, $1, $2, $3, $4) => {
        switch ($1) {
          case '#':
            obj.attrs.id = $2
            break
          case '.':
            obj.classes.push($2)
            break
          case ':':
            if (Tools.inArray('checked disabled enabled read-only required'.split(' '), $2) !== -1) {
              obj.attrs[$2] = $2
            }
            break
        }
        if ($3 === '[') {
          const m = $4.match(/([\w\-]+)(?:\=\"([^\"]+))?/)
          if (m) {
            obj.attrs[m[1]] = m[2]
          }
        }
        return ''
      })
    }
    obj.name = tagName || 'div'
    return obj
  }
  const parseSelector = (selector) => {
    if (!selector || typeof selector !== 'string') {
      return []
    }
    selector = selector.split(/\s*,\s*/)[0]
    selector = selector.replace(/\s*(~\+|~|\+|>)\s*/g, '$1')
    return Tools.map(selector.split(/(?:>|\s+(?![^\[\]]+\]))/), (item) => {
      const siblings = Tools.map(item.split(/(?:~\+|~|\+)/), parseSelectorItem)
      const obj = siblings.pop()
      if (siblings.length) {
        obj.siblings = siblings
      }
      return obj
    }).reverse()
  }
  const getCssText = (editor, format) => {
    let name, previewFrag
    let previewCss = '',
      parentFontSize
    let previewStyles = getPreviewStyles(editor)
    if (previewStyles === '') {
      return ''
    }
    const removeVars = (val) => {
      return val.replace(/%(\w+)/g, '')
    }
    if (typeof format === 'string') {
      format = editor.formatter.get(format)
      if (!format) {
        return
      }
      format = format[0]
    }
    if ('preview' in format) {
      const previewOpt = get$a(format, 'preview')
      if (is$2(previewOpt, false)) {
        return ''
      } else {
        previewStyles = previewOpt.getOr(previewStyles)
      }
    }
    name = format.block || format.inline || 'span'
    const items = parseSelector(format.selector)
    if (items.length) {
      if (!items[0].name) {
        items[0].name = name
      }
      name = format.selector
      previewFrag = parsedSelectorToHtml(items, editor)
    } else {
      previewFrag = parsedSelectorToHtml([name], editor)
    }
    const previewElm = dom.select(name, previewFrag)[0] || previewFrag.firstChild
    each$3(format.styles, (value, name) => {
      const newValue = removeVars(value)
      if (newValue) {
        dom.setStyle(previewElm, name, newValue)
      }
    })
    each$3(format.attributes, (value, name) => {
      const newValue = removeVars(value)
      if (newValue) {
        dom.setAttrib(previewElm, name, newValue)
      }
    })
    each$3(format.classes, (value) => {
      const newValue = removeVars(value)
      if (!dom.hasClass(previewElm, newValue)) {
        dom.addClass(previewElm, newValue)
      }
    })
    editor.dispatch('PreviewFormats')
    dom.setStyles(previewFrag, {
      position: 'absolute',
      left: -65535,
    })
    editor.getBody().appendChild(previewFrag)
    parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true)
    parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0
    each$3(previewStyles.split(' '), (name) => {
      let value = dom.getStyle(previewElm, name, true)
      if (name === 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
        value = dom.getStyle(editor.getBody(), name, true)
        if (rgbaToHexString(value).toLowerCase() === '#ffffff') {
          return
        }
      }
      if (name === 'color') {
        if (rgbaToHexString(value).toLowerCase() === '#000000') {
          return
        }
      }
      if (name === 'font-size') {
        if (/em|%$/.test(value)) {
          if (parentFontSize === 0) {
            return
          }
          const numValue = parseFloat(value) / (/%$/.test(value) ? 100 : 1)
          value = numValue * parentFontSize + 'px'
        }
      }
      if (name === 'border' && value) {
        previewCss += 'padding:0 2px;'
      }
      previewCss += name + ':' + value + ';'
    })
    editor.dispatch('AfterPreviewFormats')
    dom.remove(previewFrag)
    return previewCss
  }

  const setup$r = (editor) => {
    editor.addShortcut('meta+b', '', 'Bold')
    editor.addShortcut('meta+i', '', 'Italic')
    editor.addShortcut('meta+u', '', 'Underline')
    for (let i = 1; i <= 6; i++) {
      editor.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i])
    }
    editor.addShortcut('access+7', '', ['FormatBlock', false, 'p'])
    editor.addShortcut('access+8', '', ['FormatBlock', false, 'div'])
    editor.addShortcut('access+9', '', ['FormatBlock', false, 'address'])
  }

  const Formatter = (editor) => {
    const formats = FormatRegistry(editor)
    const formatChangeState = Cell(null)
    setup$r(editor)
    setup$u(editor)
    return {
      get: formats.get,
      has: formats.has,
      register: formats.register,
      unregister: formats.unregister,
      apply: (name, vars, node) => {
        applyFormat(editor, name, vars, node)
      },
      remove: (name, vars, node, similar) => {
        removeFormat(editor, name, vars, node, similar)
      },
      toggle: (name, vars, node) => {
        toggleFormat(editor, name, vars, node)
      },
      match: (name, vars, node, similar) => matchFormat(editor, name, vars, node, similar),
      closest: (names) => closestFormat(editor, names),
      matchAll: (names, vars) => matchAllFormats(editor, names, vars),
      matchNode: (node, name, vars, similar) => matchNodeFormat(editor, node, name, vars, similar),
      canApply: (name) => canApplyFormat(editor, name),
      formatChanged: (formats, callback, similar, vars) =>
        formatChanged(editor, formatChangeState, formats, callback, similar, vars),
      getCssText: curry(getCssText, editor),
    }
  }

  const shouldIgnoreCommand = (cmd) => {
    switch (cmd.toLowerCase()) {
      case 'undo':
      case 'redo':
      case 'mcefocus':
        return true
      default:
        return false
    }
  }
  const registerEvents = (editor, undoManager, locks) => {
    const isFirstTypedCharacter = Cell(false)
    const addNonTypingUndoLevel = (e) => {
      setTyping(undoManager, false, locks)
      undoManager.add({}, e)
    }
    editor.on('init', () => {
      undoManager.add()
    })
    editor.on('BeforeExecCommand', (e) => {
      const cmd = e.command
      if (!shouldIgnoreCommand(cmd)) {
        endTyping(undoManager, locks)
        undoManager.beforeChange()
      }
    })
    editor.on('ExecCommand', (e) => {
      const cmd = e.command
      if (!shouldIgnoreCommand(cmd)) {
        addNonTypingUndoLevel(e)
      }
    })
    editor.on('ObjectResizeStart cut', () => {
      undoManager.beforeChange()
    })
    editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel)
    editor.on('dragend', addNonTypingUndoLevel)
    editor.on('keyup', (e) => {
      const keyCode = e.keyCode
      if (e.isDefaultPrevented()) {
        return
      }
      if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45 || e.ctrlKey) {
        addNonTypingUndoLevel()
        editor.nodeChanged()
      }
      if (keyCode === 46 || keyCode === 8) {
        editor.nodeChanged()
      }
      if (
        isFirstTypedCharacter.get() &&
        undoManager.typing &&
        isEq$1(createFromEditor(editor), undoManager.data[0]) === false
      ) {
        if (editor.isDirty() === false) {
          editor.setDirty(true)
        }
        editor.dispatch('TypingUndo')
        isFirstTypedCharacter.set(false)
        editor.nodeChanged()
      }
    })
    editor.on('keydown', (e) => {
      const keyCode = e.keyCode
      if (e.isDefaultPrevented()) {
        return
      }
      if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45) {
        if (undoManager.typing) {
          addNonTypingUndoLevel(e)
        }
        return
      }
      const modKey = (e.ctrlKey && !e.altKey) || e.metaKey
      if ((keyCode < 16 || keyCode > 20) && keyCode !== 224 && keyCode !== 91 && !undoManager.typing && !modKey) {
        undoManager.beforeChange()
        setTyping(undoManager, true, locks)
        undoManager.add({}, e)
        isFirstTypedCharacter.set(true)
      }
    })
    editor.on('mousedown', (e) => {
      if (undoManager.typing) {
        addNonTypingUndoLevel(e)
      }
    })
    const isInsertReplacementText = (event) => event.inputType === 'insertReplacementText'
    const isInsertTextDataNull = (event) => event.inputType === 'insertText' && event.data === null
    const isInsertFromPasteOrDrop = (event) =>
      event.inputType === 'insertFromPaste' || event.inputType === 'insertFromDrop'
    editor.on('input', (e) => {
      if (e.inputType && (isInsertReplacementText(e) || isInsertTextDataNull(e) || isInsertFromPasteOrDrop(e))) {
        addNonTypingUndoLevel(e)
      }
    })
    editor.on('AddUndo Undo Redo ClearUndos', (e) => {
      if (!e.isDefaultPrevented()) {
        editor.nodeChanged()
      }
    })
  }
  const addKeyboardShortcuts = (editor) => {
    editor.addShortcut('meta+z', '', 'Undo')
    editor.addShortcut('meta+y,meta+shift+z', '', 'Redo')
  }

  const UndoManager = (editor) => {
    const beforeBookmark = value$2()
    const locks = Cell(0)
    const index = Cell(0)
    const undoManager = {
      data: [],
      typing: false,
      beforeChange: () => {
        beforeChange(editor, locks, beforeBookmark)
      },
      add: (level, event) => {
        return addUndoLevel(editor, undoManager, index, locks, beforeBookmark, level, event)
      },
      dispatchChange: () => {
        editor.setDirty(true)
        editor.dispatch('change', {
          level: createFromEditor(editor),
          lastLevel: get$b(undoManager.data, index.get()).getOrUndefined(),
        })
      },
      undo: () => {
        return undo(editor, undoManager, locks, index)
      },
      redo: () => {
        return redo(editor, index, undoManager.data)
      },
      clear: () => {
        clear(editor, undoManager, index)
      },
      reset: () => {
        reset(editor, undoManager)
      },
      hasUndo: () => {
        return hasUndo(editor, undoManager, index)
      },
      hasRedo: () => {
        return hasRedo(editor, undoManager, index)
      },
      transact: (callback) => {
        return transact(editor, undoManager, locks, callback)
      },
      ignore: (callback) => {
        ignore(editor, locks, callback)
      },
      extra: (callback1, callback2) => {
        extra(editor, undoManager, index, callback1, callback2)
      },
    }
    if (!isRtc(editor)) {
      registerEvents(editor, undoManager, locks)
    }
    addKeyboardShortcuts(editor)
    return undoManager
  }

  const nonTypingKeycodes = [
    9,
    27,
    VK.HOME,
    VK.END,
    19,
    20,
    44,
    144,
    145,
    33,
    34,
    45,
    16,
    17,
    18,
    91,
    92,
    93,
    VK.DOWN,
    VK.UP,
    VK.LEFT,
    VK.RIGHT,
  ].concat(Env.browser.isFirefox() ? [224] : [])
  const placeholderAttr = 'data-mce-placeholder'
  const isKeyboardEvent = (e) => e.type === 'keydown' || e.type === 'keyup'
  const isDeleteEvent = (e) => {
    const keyCode = e.keyCode
    return keyCode === VK.BACKSPACE || keyCode === VK.DELETE
  }
  const isNonTypingKeyboardEvent = (e) => {
    if (isKeyboardEvent(e)) {
      const keyCode = e.keyCode
      return (
        !isDeleteEvent(e) &&
        (VK.metaKeyPressed(e) ||
          e.altKey ||
          (keyCode >= 112 && keyCode <= 123) ||
          contains$2(nonTypingKeycodes, keyCode))
      )
    } else {
      return false
    }
  }
  const isTypingKeyboardEvent = (e) =>
    isKeyboardEvent(e) && !(isDeleteEvent(e) || (e.type === 'keyup' && e.keyCode === 229))
  const isVisuallyEmpty = (dom, rootElm, forcedRootBlock) => {
    if (isEmpty$2(SugarElement.fromDom(rootElm), false)) {
      const firstElement = rootElm.firstElementChild
      if (!firstElement) {
        return true
      } else if (
        dom.getStyle(rootElm.firstElementChild, 'padding-left') ||
        dom.getStyle(rootElm.firstElementChild, 'padding-right')
      ) {
        return false
      } else {
        return forcedRootBlock === firstElement.nodeName.toLowerCase()
      }
    } else {
      return false
    }
  }
  const setup$q = (editor) => {
    const dom = editor.dom
    const rootBlock = getForcedRootBlock(editor)
    const placeholder = getPlaceholder(editor)
    const updatePlaceholder = (e, initial) => {
      if (isNonTypingKeyboardEvent(e)) {
        return
      }
      const body = editor.getBody()
      const showPlaceholder = isTypingKeyboardEvent(e) ? false : isVisuallyEmpty(dom, body, rootBlock)
      const isPlaceholderShown = dom.getAttrib(body, placeholderAttr) !== ''
      if (isPlaceholderShown !== showPlaceholder || initial) {
        dom.setAttrib(body, placeholderAttr, showPlaceholder ? placeholder : null)
        dom.setAttrib(body, 'aria-placeholder', showPlaceholder ? placeholder : null)
        firePlaceholderToggle(editor, showPlaceholder)
        editor.on(showPlaceholder ? 'keydown' : 'keyup', updatePlaceholder)
        editor.off(showPlaceholder ? 'keyup' : 'keydown', updatePlaceholder)
      }
    }
    if (placeholder) {
      editor.on('init', (e) => {
        updatePlaceholder(e, true)
        editor.on('change SetContent ExecCommand', updatePlaceholder)
        editor.on('paste', (e) => Delay.setEditorTimeout(editor, () => updatePlaceholder(e)))
      })
    }
  }

  const blockPosition = (block, position) => ({
    block,
    position,
  })
  const blockBoundary = (from, to) => ({
    from,
    to,
  })
  const getBlockPosition = (rootNode, pos) => {
    const rootElm = SugarElement.fromDom(rootNode)
    const containerElm = SugarElement.fromDom(pos.container())
    return getParentBlock$2(rootElm, containerElm).map((block) => blockPosition(block, pos))
  }
  const isDifferentBlocks = (blockBoundary) => eq(blockBoundary.from.block, blockBoundary.to.block) === false
  const hasSameParent = (blockBoundary) =>
    parent(blockBoundary.from.block)
      .bind((parent1) => parent(blockBoundary.to.block).filter((parent2) => eq(parent1, parent2)))
      .isSome()
  const isEditable$2 = (blockBoundary) =>
    isContentEditableFalse$b(blockBoundary.from.block.dom) === false &&
    isContentEditableFalse$b(blockBoundary.to.block.dom) === false
  const skipLastBr = (rootNode, forward, blockPosition) => {
    if (isBr$6(blockPosition.position.getNode()) && isEmpty$2(blockPosition.block) === false) {
      return positionIn(false, blockPosition.block.dom)
        .bind((lastPositionInBlock) => {
          if (lastPositionInBlock.isEqual(blockPosition.position)) {
            return fromPosition(forward, rootNode, lastPositionInBlock).bind((to) => getBlockPosition(rootNode, to))
          } else {
            return Optional.some(blockPosition)
          }
        })
        .getOr(blockPosition)
    } else {
      return blockPosition
    }
  }
  const readFromRange = (rootNode, forward, rng) => {
    const fromBlockPos = getBlockPosition(rootNode, CaretPosition.fromRangeStart(rng))
    const toBlockPos = fromBlockPos.bind((blockPos) =>
      fromPosition(forward, rootNode, blockPos.position).bind((to) =>
        getBlockPosition(rootNode, to).map((blockPos) => skipLastBr(rootNode, forward, blockPos))
      )
    )
    return lift2(fromBlockPos, toBlockPos, blockBoundary).filter(
      (blockBoundary) => isDifferentBlocks(blockBoundary) && hasSameParent(blockBoundary) && isEditable$2(blockBoundary)
    )
  }
  const read$1 = (rootNode, forward, rng) => (rng.collapsed ? readFromRange(rootNode, forward, rng) : Optional.none())

  const getChildrenUntilBlockBoundary = (block) => {
    const children$1 = children(block)
    return findIndex$2(children$1, isBlock$2).fold(constant(children$1), (index) => children$1.slice(0, index))
  }
  const extractChildren = (block) => {
    const children = getChildrenUntilBlockBoundary(block)
    each$f(children, remove$6)
    return children
  }
  const removeEmptyRoot = (rootNode, block) => {
    const parents = parentsAndSelf(block, rootNode)
    return find$2(parents.reverse(), (element) => isEmpty$2(element)).each(remove$6)
  }
  const isEmptyBefore = (el) => filter$6(prevSiblings(el), (el) => !isEmpty$2(el)).length === 0
  const nestedBlockMerge = (rootNode, fromBlock, toBlock, insertionPoint) => {
    if (isEmpty$2(toBlock)) {
      fillWithPaddingBr(toBlock)
      return firstPositionIn(toBlock.dom)
    }
    if (isEmptyBefore(insertionPoint) && isEmpty$2(fromBlock)) {
      before$3(insertionPoint, SugarElement.fromTag('br'))
    }
    const position = prevPosition(toBlock.dom, CaretPosition.before(insertionPoint.dom))
    each$f(extractChildren(fromBlock), (child) => {
      before$3(insertionPoint, child)
    })
    removeEmptyRoot(rootNode, fromBlock)
    return position
  }
  const sidelongBlockMerge = (rootNode, fromBlock, toBlock) => {
    if (isEmpty$2(toBlock)) {
      remove$6(toBlock)
      if (isEmpty$2(fromBlock)) {
        fillWithPaddingBr(fromBlock)
      }
      return firstPositionIn(fromBlock.dom)
    }
    const position = lastPositionIn(toBlock.dom)
    each$f(extractChildren(fromBlock), (child) => {
      append$1(toBlock, child)
    })
    removeEmptyRoot(rootNode, fromBlock)
    return position
  }
  const findInsertionPoint = (toBlock, block) => {
    const parentsAndSelf$1 = parentsAndSelf(block, toBlock)
    return Optional.from(parentsAndSelf$1[parentsAndSelf$1.length - 1])
  }
  const getInsertionPoint = (fromBlock, toBlock) =>
    contains(toBlock, fromBlock) ? findInsertionPoint(toBlock, fromBlock) : Optional.none()
  const trimBr = (first, block) => {
    positionIn(first, block.dom)
      .map((position) => position.getNode())
      .map(SugarElement.fromDom)
      .filter(isBr$5)
      .each(remove$6)
  }
  const mergeBlockInto = (rootNode, fromBlock, toBlock) => {
    trimBr(true, fromBlock)
    trimBr(false, toBlock)
    return getInsertionPoint(fromBlock, toBlock).fold(
      curry(sidelongBlockMerge, rootNode, fromBlock, toBlock),
      curry(nestedBlockMerge, rootNode, fromBlock, toBlock)
    )
  }
  const mergeBlocks = (rootNode, forward, block1, block2) =>
    forward ? mergeBlockInto(rootNode, block2, block1) : mergeBlockInto(rootNode, block1, block2)

  const backspaceDelete$8 = (editor, forward) => {
    const rootNode = SugarElement.fromDom(editor.getBody())
    const position = read$1(rootNode.dom, forward, editor.selection.getRng()).map((blockBoundary) => () => {
      mergeBlocks(rootNode, forward, blockBoundary.from.block, blockBoundary.to.block).each((pos) => {
        editor.selection.setRng(pos.toRange())
      })
    })
    return position
  }

  const deleteRangeMergeBlocks = (rootNode, selection) => {
    const rng = selection.getRng()
    return lift2(
      getParentBlock$2(rootNode, SugarElement.fromDom(rng.startContainer)),
      getParentBlock$2(rootNode, SugarElement.fromDom(rng.endContainer)),
      (block1, block2) => {
        if (eq(block1, block2) === false) {
          return Optional.some(() => {
            rng.deleteContents()
            mergeBlocks(rootNode, true, block1, block2).each((pos) => {
              selection.setRng(pos.toRange())
            })
          })
        } else {
          return Optional.none()
        }
      }
    ).getOr(Optional.none())
  }
  const isRawNodeInTable = (root, rawNode) => {
    const node = SugarElement.fromDom(rawNode)
    const isRoot = curry(eq, root)
    return ancestor$3(node, isTableCell$4, isRoot).isSome()
  }
  const isSelectionInTable = (root, rng) =>
    isRawNodeInTable(root, rng.startContainer) || isRawNodeInTable(root, rng.endContainer)
  const isEverythingSelected = (root, rng) => {
    const noPrevious = prevPosition(root.dom, CaretPosition.fromRangeStart(rng)).isNone()
    const noNext = nextPosition(root.dom, CaretPosition.fromRangeEnd(rng)).isNone()
    return !isSelectionInTable(root, rng) && noPrevious && noNext
  }
  const emptyEditor = (editor) => {
    return Optional.some(() => {
      editor.setContent('')
      editor.selection.setCursorLocation()
    })
  }
  const deleteRange$1 = (editor) => {
    const rootNode = SugarElement.fromDom(editor.getBody())
    const rng = editor.selection.getRng()
    return isEverythingSelected(rootNode, rng)
      ? emptyEditor(editor)
      : deleteRangeMergeBlocks(rootNode, editor.selection)
  }
  const backspaceDelete$7 = (editor, _forward) =>
    editor.selection.isCollapsed() ? Optional.none() : deleteRange$1(editor)

  const isContentEditableTrue$2 = isContentEditableTrue$5
  const isContentEditableFalse$5 = isContentEditableFalse$b
  const showCaret = (direction, editor, node, before, scrollIntoView) =>
    Optional.from(editor._selectionOverrides.showCaret(direction, node, before, scrollIntoView))
  const getNodeRange = (node) => {
    const rng = node.ownerDocument.createRange()
    rng.selectNode(node)
    return rng
  }
  const selectNode = (editor, node) => {
    const e = editor.dispatch('BeforeObjectSelected', { target: node })
    if (e.isDefaultPrevented()) {
      return Optional.none()
    }
    return Optional.some(getNodeRange(node))
  }
  const renderCaretAtRange = (editor, range, scrollIntoView) => {
    const normalizedRange = normalizeRange(1, editor.getBody(), range)
    const caretPosition = CaretPosition.fromRangeStart(normalizedRange)
    const caretPositionNode = caretPosition.getNode()
    if (isInlineFakeCaretTarget(caretPositionNode)) {
      return showCaret(1, editor, caretPositionNode, !caretPosition.isAtEnd(), false)
    }
    const caretPositionBeforeNode = caretPosition.getNode(true)
    if (isInlineFakeCaretTarget(caretPositionBeforeNode)) {
      return showCaret(1, editor, caretPositionBeforeNode, false, false)
    }
    const ceRoot = editor.dom.getParent(
      caretPosition.getNode(),
      (node) => isContentEditableFalse$5(node) || isContentEditableTrue$2(node)
    )
    if (isInlineFakeCaretTarget(ceRoot)) {
      return showCaret(1, editor, ceRoot, false, scrollIntoView)
    }
    return Optional.none()
  }
  const renderRangeCaret = (editor, range, scrollIntoView) =>
    range.collapsed ? renderCaretAtRange(editor, range, scrollIntoView).getOr(range) : range

  const isBeforeBoundary = (pos) => isBeforeContentEditableFalse(pos) || isBeforeMedia(pos)
  const isAfterBoundary = (pos) => isAfterContentEditableFalse(pos) || isAfterMedia(pos)
  const trimEmptyTextNode = (dom, node) => {
    if (isText$9(node) && node.data.length === 0) {
      dom.remove(node)
    }
  }
  const deleteContentAndShowCaret = (editor, range, node, direction, forward, peekCaretPosition) => {
    showCaret(direction, editor, peekCaretPosition.getNode(!forward), forward, true).each((caretRange) => {
      if (range.collapsed) {
        const deleteRange = range.cloneRange()
        if (forward) {
          deleteRange.setEnd(caretRange.startContainer, caretRange.startOffset)
        } else {
          deleteRange.setStart(caretRange.endContainer, caretRange.endOffset)
        }
        deleteRange.deleteContents()
      } else {
        range.deleteContents()
      }
      editor.selection.setRng(caretRange)
    })
    trimEmptyTextNode(editor.dom, node)
  }
  const deleteBoundaryText = (editor, forward) => {
    const range = editor.selection.getRng()
    if (!isText$9(range.commonAncestorContainer)) {
      return Optional.none()
    }
    const direction = forward ? HDirection.Forwards : HDirection.Backwards
    const caretWalker = CaretWalker(editor.getBody())
    const getNextPosFn = curry(getVisualCaretPosition, forward ? caretWalker.next : caretWalker.prev)
    const isBeforeFn = forward ? isBeforeBoundary : isAfterBoundary
    const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range)
    const nextCaretPosition = normalizePosition(forward, getNextPosFn(caretPosition))
    if (!nextCaretPosition || !isMoveInsideSameBlock(caretPosition, nextCaretPosition)) {
      return Optional.none()
    } else if (isBeforeFn(nextCaretPosition)) {
      return Optional.some(() =>
        deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, nextCaretPosition)
      )
    }
    const peekCaretPosition = getNextPosFn(nextCaretPosition)
    if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
      if (isMoveInsideSameBlock(nextCaretPosition, peekCaretPosition)) {
        return Optional.some(() =>
          deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, peekCaretPosition)
        )
      }
    }
    return Optional.none()
  }
  const backspaceDelete$6 = (editor, forward) => deleteBoundaryText(editor, forward)

  const getEdgeCefPosition = (editor, atStart) => {
    const root = editor.getBody()
    return atStart
      ? firstPositionIn(root).filter(isBeforeContentEditableFalse)
      : lastPositionIn(root).filter(isAfterContentEditableFalse)
  }
  const isCefAtEdgeSelected = (editor) => {
    const rng = editor.selection.getRng()
    return (
      !rng.collapsed &&
      (getEdgeCefPosition(editor, true).exists((pos) => pos.isEqual(CaretPosition.fromRangeStart(rng))) ||
        getEdgeCefPosition(editor, false).exists((pos) => pos.isEqual(CaretPosition.fromRangeEnd(rng))))
    )
  }

  const isCompoundElement = (node) =>
    isTableCell$4(SugarElement.fromDom(node)) || isListItem(SugarElement.fromDom(node))
  const DeleteAction = Adt.generate([
    { remove: ['element'] },
    { moveToElement: ['element'] },
    { moveToPosition: ['position'] },
  ])
  const isAtContentEditableBlockCaret = (forward, from) => {
    const elm = from.getNode(forward === false)
    const caretLocation = forward ? 'after' : 'before'
    return isElement$6(elm) && elm.getAttribute('data-mce-caret') === caretLocation
  }
  const isDeleteFromCefDifferentBlocks = (root, forward, from, to) => {
    const inSameBlock = (elm) => isInline$1(SugarElement.fromDom(elm)) && !isInSameBlock(from, to, root)
    return getRelativeCefElm(!forward, from).fold(
      () => getRelativeCefElm(forward, to).fold(never, inSameBlock),
      inSameBlock
    )
  }
  const deleteEmptyBlockOrMoveToCef = (root, forward, from, to) => {
    const toCefElm = to.getNode(forward === false)
    return getParentBlock$2(SugarElement.fromDom(root), SugarElement.fromDom(from.getNode()))
      .map((blockElm) =>
        isEmpty$2(blockElm) ? DeleteAction.remove(blockElm.dom) : DeleteAction.moveToElement(toCefElm)
      )
      .orThunk(() => Optional.some(DeleteAction.moveToElement(toCefElm)))
  }
  const findCefPosition = (root, forward, from) =>
    fromPosition(forward, root, from).bind((to) => {
      if (isCompoundElement(to.getNode())) {
        return Optional.none()
      } else if (isDeleteFromCefDifferentBlocks(root, forward, from, to)) {
        return Optional.none()
      } else if (forward && isContentEditableFalse$b(to.getNode())) {
        return deleteEmptyBlockOrMoveToCef(root, forward, from, to)
      } else if (forward === false && isContentEditableFalse$b(to.getNode(true))) {
        return deleteEmptyBlockOrMoveToCef(root, forward, from, to)
      } else if (forward && isAfterContentEditableFalse(from)) {
        return Optional.some(DeleteAction.moveToPosition(to))
      } else if (forward === false && isBeforeContentEditableFalse(from)) {
        return Optional.some(DeleteAction.moveToPosition(to))
      } else {
        return Optional.none()
      }
    })
  const getContentEditableBlockAction = (forward, elm) => {
    if (forward && isContentEditableFalse$b(elm.nextSibling)) {
      return Optional.some(DeleteAction.moveToElement(elm.nextSibling))
    } else if (forward === false && isContentEditableFalse$b(elm.previousSibling)) {
      return Optional.some(DeleteAction.moveToElement(elm.previousSibling))
    } else {
      return Optional.none()
    }
  }
  const skipMoveToActionFromInlineCefToContent = (root, from, deleteAction) =>
    deleteAction.fold(
      (elm) => Optional.some(DeleteAction.remove(elm)),
      (elm) => Optional.some(DeleteAction.moveToElement(elm)),
      (to) => {
        if (isInSameBlock(from, to, root)) {
          return Optional.none()
        } else {
          return Optional.some(DeleteAction.moveToPosition(to))
        }
      }
    )
  const getContentEditableAction = (root, forward, from) => {
    if (isAtContentEditableBlockCaret(forward, from)) {
      return getContentEditableBlockAction(forward, from.getNode(forward === false)).fold(
        () => findCefPosition(root, forward, from),
        Optional.some
      )
    } else {
      return findCefPosition(root, forward, from).bind((deleteAction) =>
        skipMoveToActionFromInlineCefToContent(root, from, deleteAction)
      )
    }
  }
  const read = (root, forward, rng) => {
    const normalizedRange = normalizeRange(forward ? 1 : -1, root, rng)
    const from = CaretPosition.fromRangeStart(normalizedRange)
    const rootElement = SugarElement.fromDom(root)
    if (forward === false && isAfterContentEditableFalse(from)) {
      return Optional.some(DeleteAction.remove(from.getNode(true)))
    } else if (forward && isBeforeContentEditableFalse(from)) {
      return Optional.some(DeleteAction.remove(from.getNode()))
    } else if (forward === false && isBeforeContentEditableFalse(from) && isAfterBr(rootElement, from)) {
      return findPreviousBr(rootElement, from).map((br) => DeleteAction.remove(br.getNode()))
    } else if (forward && isAfterContentEditableFalse(from) && isBeforeBr$1(rootElement, from)) {
      return findNextBr(rootElement, from).map((br) => DeleteAction.remove(br.getNode()))
    } else {
      return getContentEditableAction(root, forward, from)
    }
  }

  const deleteElement$1 = (editor, forward) => (element) => {
    editor._selectionOverrides.hideFakeCaret()
    deleteElement$2(editor, forward, SugarElement.fromDom(element))
    return true
  }
  const moveToElement = (editor, forward) => (element) => {
    const pos = forward ? CaretPosition.before(element) : CaretPosition.after(element)
    editor.selection.setRng(pos.toRange())
    return true
  }
  const moveToPosition = (editor) => (pos) => {
    editor.selection.setRng(pos.toRange())
    return true
  }
  const getAncestorCe = (editor, node) => Optional.from(getContentEditableRoot$1(editor.getBody(), node))
  const backspaceDeleteCaret = (editor, forward) => {
    const selectedNode = editor.selection.getNode()
    return getAncestorCe(editor, selectedNode)
      .filter(isContentEditableFalse$b)
      .fold(
        () =>
          read(editor.getBody(), forward, editor.selection.getRng()).map(
            (deleteAction) => () =>
              deleteAction.fold(
                deleteElement$1(editor, forward),
                moveToElement(editor, forward),
                moveToPosition(editor)
              )
          ),
        () => Optional.some(noop)
      )
  }
  const deleteOffscreenSelection = (rootElement) => {
    each$f(descendants(rootElement, '.mce-offscreen-selection'), remove$6)
  }
  const backspaceDeleteRange = (editor, forward) => {
    const selectedNode = editor.selection.getNode()
    if (isContentEditableFalse$b(selectedNode) && !isTableCell$5(selectedNode)) {
      const hasCefAncestor = getAncestorCe(editor, selectedNode.parentNode).filter(isContentEditableFalse$b)
      return hasCefAncestor.fold(
        () =>
          Optional.some(() => {
            deleteOffscreenSelection(SugarElement.fromDom(editor.getBody()))
            deleteElement$2(editor, forward, SugarElement.fromDom(editor.selection.getNode()))
            paddEmptyBody(editor)
          }),
        () => Optional.some(noop)
      )
    }
    if (isCefAtEdgeSelected(editor)) {
      return Optional.some(() => {
        deleteRangeContents(editor, editor.selection.getRng(), SugarElement.fromDom(editor.getBody()))
      })
    }
    return Optional.none()
  }
  const paddEmptyElement = (editor) => {
    const dom = editor.dom,
      selection = editor.selection
    const ceRoot = getContentEditableRoot$1(editor.getBody(), selection.getNode())
    if (isContentEditableTrue$5(ceRoot) && dom.isBlock(ceRoot) && dom.isEmpty(ceRoot)) {
      const br = dom.create('br', { 'data-mce-bogus': '1' })
      dom.setHTML(ceRoot, '')
      ceRoot.appendChild(br)
      selection.setRng(CaretPosition.before(br).toRange())
    }
    return true
  }
  const backspaceDelete$5 = (editor, forward) => {
    if (editor.selection.isCollapsed()) {
      return backspaceDeleteCaret(editor, forward)
    } else {
      return backspaceDeleteRange(editor, forward)
    }
  }

  const deleteCaret$2 = (editor, forward) => {
    const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng())
    return fromPosition(forward, editor.getBody(), fromPos)
      .filter((pos) => (forward ? isBeforeImageBlock(pos) : isAfterImageBlock(pos)))
      .bind((pos) => Optional.from(getChildNodeAtRelativeOffset(forward ? 0 : -1, pos)))
      .map((elm) => () => editor.selection.select(elm))
  }
  const backspaceDelete$4 = (editor, forward) =>
    editor.selection.isCollapsed() ? deleteCaret$2(editor, forward) : Optional.none()

  const isText$2 = isText$9
  const startsWithCaretContainer = (node) => isText$2(node) && node.data[0] === ZWSP$1
  const endsWithCaretContainer = (node) => isText$2(node) && node.data[node.data.length - 1] === ZWSP$1
  const createZwsp = (node) => node.ownerDocument.createTextNode(ZWSP$1)
  const insertBefore = (node) => {
    if (isText$2(node.previousSibling)) {
      if (endsWithCaretContainer(node.previousSibling)) {
        return node.previousSibling
      } else {
        node.previousSibling.appendData(ZWSP$1)
        return node.previousSibling
      }
    } else if (isText$2(node)) {
      if (startsWithCaretContainer(node)) {
        return node
      } else {
        node.insertData(0, ZWSP$1)
        return node
      }
    } else {
      const newNode = createZwsp(node)
      node.parentNode.insertBefore(newNode, node)
      return newNode
    }
  }
  const insertAfter = (node) => {
    if (isText$2(node.nextSibling)) {
      if (startsWithCaretContainer(node.nextSibling)) {
        return node.nextSibling
      } else {
        node.nextSibling.insertData(0, ZWSP$1)
        return node.nextSibling
      }
    } else if (isText$2(node)) {
      if (endsWithCaretContainer(node)) {
        return node
      } else {
        node.appendData(ZWSP$1)
        return node
      }
    } else {
      const newNode = createZwsp(node)
      if (node.nextSibling) {
        node.parentNode.insertBefore(newNode, node.nextSibling)
      } else {
        node.parentNode.appendChild(newNode)
      }
      return newNode
    }
  }
  const insertInline = (before, node) => (before ? insertBefore(node) : insertAfter(node))
  const insertInlineBefore = curry(insertInline, true)
  const insertInlineAfter = curry(insertInline, false)

  const insertInlinePos = (pos, before) => {
    if (isText$9(pos.container())) {
      return insertInline(before, pos.container())
    } else {
      return insertInline(before, pos.getNode())
    }
  }
  const isPosCaretContainer = (pos, caret) => {
    const caretNode = caret.get()
    return caretNode && pos.container() === caretNode && isCaretContainerInline(caretNode)
  }
  const renderCaret = (caret, location) =>
    location.fold(
      (element) => {
        remove$4(caret.get())
        const text = insertInlineBefore(element)
        caret.set(text)
        return Optional.some(CaretPosition(text, text.length - 1))
      },
      (element) =>
        firstPositionIn(element).map((pos) => {
          if (!isPosCaretContainer(pos, caret)) {
            remove$4(caret.get())
            const text = insertInlinePos(pos, true)
            caret.set(text)
            return CaretPosition(text, 1)
          } else {
            return CaretPosition(caret.get(), 1)
          }
        }),
      (element) =>
        lastPositionIn(element).map((pos) => {
          if (!isPosCaretContainer(pos, caret)) {
            remove$4(caret.get())
            const text = insertInlinePos(pos, false)
            caret.set(text)
            return CaretPosition(text, text.length - 1)
          } else {
            return CaretPosition(caret.get(), caret.get().length - 1)
          }
        }),
      (element) => {
        remove$4(caret.get())
        const text = insertInlineAfter(element)
        caret.set(text)
        return Optional.some(CaretPosition(text, 1))
      }
    )

  const evaluateUntil = (fns, args) => {
    for (let i = 0; i < fns.length; i++) {
      const result = fns[i].apply(null, args)
      if (result.isSome()) {
        return result
      }
    }
    return Optional.none()
  }

  const Location = Adt.generate([
    { before: ['element'] },
    { start: ['element'] },
    { end: ['element'] },
    { after: ['element'] },
  ])
  const rescope$1 = (rootNode, node) => {
    const parentBlock = getParentBlock$3(node, rootNode)
    return parentBlock ? parentBlock : rootNode
  }
  const before = (isInlineTarget, rootNode, pos) => {
    const nPos = normalizeForwards(pos)
    const scope = rescope$1(rootNode, nPos.container())
    return findRootInline(isInlineTarget, scope, nPos).fold(
      () =>
        nextPosition(scope, nPos)
          .bind(curry(findRootInline, isInlineTarget, scope))
          .map((inline) => Location.before(inline)),
      Optional.none
    )
  }
  const isNotInsideFormatCaretContainer = (rootNode, elm) => getParentCaretContainer(rootNode, elm) === null
  const findInsideRootInline = (isInlineTarget, rootNode, pos) =>
    findRootInline(isInlineTarget, rootNode, pos).filter(curry(isNotInsideFormatCaretContainer, rootNode))
  const start$1 = (isInlineTarget, rootNode, pos) => {
    const nPos = normalizeBackwards(pos)
    return findInsideRootInline(isInlineTarget, rootNode, nPos).bind((inline) => {
      const prevPos = prevPosition(inline, nPos)
      return prevPos.isNone() ? Optional.some(Location.start(inline)) : Optional.none()
    })
  }
  const end = (isInlineTarget, rootNode, pos) => {
    const nPos = normalizeForwards(pos)
    return findInsideRootInline(isInlineTarget, rootNode, nPos).bind((inline) => {
      const nextPos = nextPosition(inline, nPos)
      return nextPos.isNone() ? Optional.some(Location.end(inline)) : Optional.none()
    })
  }
  const after = (isInlineTarget, rootNode, pos) => {
    const nPos = normalizeBackwards(pos)
    const scope = rescope$1(rootNode, nPos.container())
    return findRootInline(isInlineTarget, scope, nPos).fold(
      () =>
        prevPosition(scope, nPos)
          .bind(curry(findRootInline, isInlineTarget, scope))
          .map((inline) => Location.after(inline)),
      Optional.none
    )
  }
  const isValidLocation = (location) => isRtl(getElement(location)) === false
  const readLocation = (isInlineTarget, rootNode, pos) => {
    const location = evaluateUntil([before, start$1, end, after], [isInlineTarget, rootNode, pos])
    return location.filter(isValidLocation)
  }
  const getElement = (location) => location.fold(identity, identity, identity, identity)
  const getName = (location) => location.fold(constant('before'), constant('start'), constant('end'), constant('after'))
  const outside = (location) => location.fold(Location.before, Location.before, Location.after, Location.after)
  const inside = (location) => location.fold(Location.start, Location.start, Location.end, Location.end)
  const isEq = (location1, location2) =>
    getName(location1) === getName(location2) && getElement(location1) === getElement(location2)
  const betweenInlines = (forward, isInlineTarget, rootNode, from, to, location) =>
    lift2(
      findRootInline(isInlineTarget, rootNode, from),
      findRootInline(isInlineTarget, rootNode, to),
      (fromInline, toInline) => {
        if (fromInline !== toInline && hasSameParentBlock(rootNode, fromInline, toInline)) {
          return Location.after(forward ? fromInline : toInline)
        } else {
          return location
        }
      }
    ).getOr(location)
  const skipNoMovement = (fromLocation, toLocation) =>
    fromLocation.fold(always, (fromLocation) => !isEq(fromLocation, toLocation))
  const findLocationTraverse = (forward, isInlineTarget, rootNode, fromLocation, pos) => {
    const from = normalizePosition(forward, pos)
    const to = fromPosition(forward, rootNode, from).map(curry(normalizePosition, forward))
    const location = to.fold(
      () => fromLocation.map(outside),
      (to) =>
        readLocation(isInlineTarget, rootNode, to)
          .map(curry(betweenInlines, forward, isInlineTarget, rootNode, from, to))
          .filter(curry(skipNoMovement, fromLocation))
    )
    return location.filter(isValidLocation)
  }
  const findLocationSimple = (forward, location) => {
    if (forward) {
      return location.fold(
        compose(Optional.some, Location.start),
        Optional.none,
        compose(Optional.some, Location.after),
        Optional.none
      )
    } else {
      return location.fold(
        Optional.none,
        compose(Optional.some, Location.before),
        Optional.none,
        compose(Optional.some, Location.end)
      )
    }
  }
  const findLocation$1 = (forward, isInlineTarget, rootNode, pos) => {
    const from = normalizePosition(forward, pos)
    const fromLocation = readLocation(isInlineTarget, rootNode, from)
    return readLocation(isInlineTarget, rootNode, from)
      .bind(curry(findLocationSimple, forward))
      .orThunk(() => findLocationTraverse(forward, isInlineTarget, rootNode, fromLocation, pos))
  }

  const hasSelectionModifyApi = (editor) => {
    return isFunction(editor.selection.getSel().modify)
  }
  const moveRel = (forward, selection, pos) => {
    const delta = forward ? 1 : -1
    selection.setRng(CaretPosition(pos.container(), pos.offset() + delta).toRange())
    selection.getSel().modify('move', forward ? 'forward' : 'backward', 'word')
    return true
  }
  const moveByWord = (forward, editor) => {
    const rng = editor.selection.getRng()
    const pos = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng)
    if (!hasSelectionModifyApi(editor)) {
      return false
    } else if (forward && isBeforeInline(pos)) {
      return moveRel(true, editor.selection, pos)
    } else if (!forward && isAfterInline(pos)) {
      return moveRel(false, editor.selection, pos)
    } else {
      return false
    }
  }

  var BreakType
  ;(function (BreakType) {
    BreakType[(BreakType['Br'] = 0)] = 'Br'
    BreakType[(BreakType['Block'] = 1)] = 'Block'
    BreakType[(BreakType['Wrap'] = 2)] = 'Wrap'
    BreakType[(BreakType['Eol'] = 3)] = 'Eol'
  })(BreakType || (BreakType = {}))
  const flip = (direction, positions) => (direction === HDirection.Backwards ? reverse(positions) : positions)
  const walk$1 = (direction, caretWalker, pos) =>
    direction === HDirection.Forwards ? caretWalker.next(pos) : caretWalker.prev(pos)
  const getBreakType = (scope, direction, currentPos, nextPos) => {
    if (isBr$6(nextPos.getNode(direction === HDirection.Forwards))) {
      return BreakType.Br
    } else if (isInSameBlock(currentPos, nextPos) === false) {
      return BreakType.Block
    } else {
      return BreakType.Wrap
    }
  }
  const getPositionsUntil = (predicate, direction, scope, start) => {
    const caretWalker = CaretWalker(scope)
    let currentPos = start
    const positions = []
    while (currentPos) {
      const nextPos = walk$1(direction, caretWalker, currentPos)
      if (!nextPos) {
        break
      }
      if (isBr$6(nextPos.getNode(false))) {
        if (direction === HDirection.Forwards) {
          return {
            positions: flip(direction, positions).concat([nextPos]),
            breakType: BreakType.Br,
            breakAt: Optional.some(nextPos),
          }
        } else {
          return {
            positions: flip(direction, positions),
            breakType: BreakType.Br,
            breakAt: Optional.some(nextPos),
          }
        }
      }
      if (!nextPos.isVisible()) {
        currentPos = nextPos
        continue
      }
      if (predicate(currentPos, nextPos)) {
        const breakType = getBreakType(scope, direction, currentPos, nextPos)
        return {
          positions: flip(direction, positions),
          breakType,
          breakAt: Optional.some(nextPos),
        }
      }
      positions.push(nextPos)
      currentPos = nextPos
    }
    return {
      positions: flip(direction, positions),
      breakType: BreakType.Eol,
      breakAt: Optional.none(),
    }
  }
  const getAdjacentLinePositions = (direction, getPositionsUntilBreak, scope, start) =>
    getPositionsUntilBreak(scope, start)
      .breakAt.map((pos) => {
        const positions = getPositionsUntilBreak(scope, pos).positions
        return direction === HDirection.Backwards ? positions.concat(pos) : [pos].concat(positions)
      })
      .getOr([])
  const findClosestHorizontalPositionFromPoint = (positions, x) =>
    foldl(
      positions,
      (acc, newPos) =>
        acc.fold(
          () => Optional.some(newPos),
          (lastPos) =>
            lift2(head(lastPos.getClientRects()), head(newPos.getClientRects()), (lastRect, newRect) => {
              const lastDist = Math.abs(x - lastRect.left)
              const newDist = Math.abs(x - newRect.left)
              return newDist <= lastDist ? newPos : lastPos
            }).or(acc)
        ),
      Optional.none()
    )
  const findClosestHorizontalPosition = (positions, pos) =>
    head(pos.getClientRects()).bind((targetRect) => findClosestHorizontalPositionFromPoint(positions, targetRect.left))
  const getPositionsUntilPreviousLine = curry(getPositionsUntil, CaretPosition.isAbove, -1)
  const getPositionsUntilNextLine = curry(getPositionsUntil, CaretPosition.isBelow, 1)
  const getPositionsAbove = curry(getAdjacentLinePositions, -1, getPositionsUntilPreviousLine)
  const getPositionsBelow = curry(getAdjacentLinePositions, 1, getPositionsUntilNextLine)
  const isAtFirstLine = (scope, pos) => getPositionsUntilPreviousLine(scope, pos).breakAt.isNone()
  const isAtLastLine = (scope, pos) => getPositionsUntilNextLine(scope, pos).breakAt.isNone()
  const getFirstLinePositions = (scope) =>
    firstPositionIn(scope)
      .map((pos) => [pos].concat(getPositionsUntilNextLine(scope, pos).positions))
      .getOr([])
  const getLastLinePositions = (scope) =>
    lastPositionIn(scope)
      .map((pos) => getPositionsUntilPreviousLine(scope, pos).positions.concat(pos))
      .getOr([])
  const getClosestPositionAbove = (scope, pos) => findClosestHorizontalPosition(getPositionsAbove(scope, pos), pos)
  const getClosestPositionBelow = (scope, pos) => findClosestHorizontalPosition(getPositionsBelow(scope, pos), pos)

  const isContentEditableFalse$4 = isContentEditableFalse$b
  const distanceToRectLeft$1 = (clientRect, clientX) => Math.abs(clientRect.left - clientX)
  const distanceToRectRight$1 = (clientRect, clientX) => Math.abs(clientRect.right - clientX)
  const isNodeClientRect = (rect) => hasNonNullableKey(rect, 'node')
  const findClosestClientRect = (clientRects, clientX) =>
    reduce(clientRects, (oldClientRect, clientRect) => {
      const oldDistance = Math.min(
        distanceToRectLeft$1(oldClientRect, clientX),
        distanceToRectRight$1(oldClientRect, clientX)
      )
      const newDistance = Math.min(
        distanceToRectLeft$1(clientRect, clientX),
        distanceToRectRight$1(clientRect, clientX)
      )
      if (newDistance === oldDistance && isNodeClientRect(clientRect) && isContentEditableFalse$4(clientRect.node)) {
        return clientRect
      }
      if (newDistance < oldDistance) {
        return clientRect
      }
      return oldClientRect
    })

  const getNodeClientRects = (node) => {
    const toArrayWithNode = (clientRects) => {
      return map$3(clientRects, (rect) => {
        const clientRect = clone$1(rect)
        clientRect.node = node
        return clientRect
      })
    }
    if (isElement$6(node)) {
      return toArrayWithNode(node.getClientRects())
    } else if (isText$9(node)) {
      const rng = node.ownerDocument.createRange()
      rng.setStart(node, 0)
      rng.setEnd(node, node.data.length)
      return toArrayWithNode(rng.getClientRects())
    } else {
      return []
    }
  }
  const getClientRects = (nodes) => bind$3(nodes, getNodeClientRects)

  var VDirection
  ;(function (VDirection) {
    VDirection[(VDirection['Up'] = -1)] = 'Up'
    VDirection[(VDirection['Down'] = 1)] = 'Down'
  })(VDirection || (VDirection = {}))
  const findUntil = (direction, root, predicateFn, node) => {
    while ((node = findNode(node, direction, isEditableCaretCandidate$1, root))) {
      if (predicateFn(node)) {
        return
      }
    }
  }
  const walkUntil = (direction, isAboveFn, isBeflowFn, root, predicateFn, caretPosition) => {
    let line = 0
    const result = []
    const add = (node) => {
      let clientRects = getClientRects([node])
      if (direction === -1) {
        clientRects = clientRects.reverse()
      }
      for (let i = 0; i < clientRects.length; i++) {
        const clientRect = clientRects[i]
        if (isBeflowFn(clientRect, targetClientRect)) {
          continue
        }
        if (result.length > 0 && isAboveFn(clientRect, last$2(result))) {
          line++
        }
        clientRect.line = line
        if (predicateFn(clientRect)) {
          return true
        }
        result.push(clientRect)
      }
    }
    const targetClientRect = last$2(caretPosition.getClientRects())
    if (!targetClientRect) {
      return result
    }
    const node = caretPosition.getNode()
    add(node)
    findUntil(direction, root, add, node)
    return result
  }
  const aboveLineNumber = (lineNumber, clientRect) => clientRect.line > lineNumber
  const isLineNumber = (lineNumber, clientRect) => clientRect.line === lineNumber
  const upUntil = curry(walkUntil, VDirection.Up, isAbove$1, isBelow$1)
  const downUntil = curry(walkUntil, VDirection.Down, isBelow$1, isAbove$1)
  const positionsUntil = (direction, root, predicateFn, node) => {
    const caretWalker = CaretWalker(root)
    let walkFn
    let isBelowFn
    let isAboveFn
    let caretPosition
    const result = []
    let line = 0
    const getClientRect = (caretPosition) => {
      if (direction === 1) {
        return last$2(caretPosition.getClientRects())
      }
      return last$2(caretPosition.getClientRects())
    }
    if (direction === 1) {
      walkFn = caretWalker.next
      isBelowFn = isBelow$1
      isAboveFn = isAbove$1
      caretPosition = CaretPosition.after(node)
    } else {
      walkFn = caretWalker.prev
      isBelowFn = isAbove$1
      isAboveFn = isBelow$1
      caretPosition = CaretPosition.before(node)
    }
    const targetClientRect = getClientRect(caretPosition)
    do {
      if (!caretPosition.isVisible()) {
        continue
      }
      const rect = getClientRect(caretPosition)
      if (isAboveFn(rect, targetClientRect)) {
        continue
      }
      if (result.length > 0 && isBelowFn(rect, last$2(result))) {
        line++
      }
      const clientRect = clone$1(rect)
      clientRect.position = caretPosition
      clientRect.line = line
      if (predicateFn(clientRect)) {
        return result
      }
      result.push(clientRect)
    } while ((caretPosition = walkFn(caretPosition)))
    return result
  }
  const isAboveLine = (lineNumber) => (clientRect) => aboveLineNumber(lineNumber, clientRect)
  const isLine = (lineNumber) => (clientRect) => isLineNumber(lineNumber, clientRect)

  const moveToRange = (editor, rng) => {
    editor.selection.setRng(rng)
    scrollRangeIntoView(editor, editor.selection.getRng())
  }
  const renderRangeCaretOpt = (editor, range, scrollIntoView) =>
    Optional.some(renderRangeCaret(editor, range, scrollIntoView))
  const moveHorizontally = (editor, direction, range, isBefore, isAfter, isElement) => {
    const forwards = direction === HDirection.Forwards
    const caretWalker = CaretWalker(editor.getBody())
    const getNextPosFn = curry(getVisualCaretPosition, forwards ? caretWalker.next : caretWalker.prev)
    const isBeforeFn = forwards ? isBefore : isAfter
    if (!range.collapsed) {
      const node = getSelectedNode(range)
      if (isElement(node)) {
        return showCaret(direction, editor, node, direction === HDirection.Backwards, false)
      } else if (isCefAtEdgeSelected(editor)) {
        const newRange = range.cloneRange()
        newRange.collapse(direction === HDirection.Backwards)
        return Optional.from(newRange)
      }
    }
    const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range)
    if (isBeforeFn(caretPosition)) {
      return selectNode(editor, caretPosition.getNode(!forwards))
    }
    const nextCaretPosition = normalizePosition(forwards, getNextPosFn(caretPosition))
    const rangeIsInContainerBlock = isRangeInCaretContainerBlock(range)
    if (!nextCaretPosition) {
      return rangeIsInContainerBlock ? Optional.some(range) : Optional.none()
    }
    if (isBeforeFn(nextCaretPosition)) {
      return showCaret(direction, editor, nextCaretPosition.getNode(!forwards), forwards, false)
    }
    const peekCaretPosition = getNextPosFn(nextCaretPosition)
    if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
      if (isMoveInsideSameBlock(nextCaretPosition, peekCaretPosition)) {
        return showCaret(direction, editor, peekCaretPosition.getNode(!forwards), forwards, false)
      }
    }
    if (rangeIsInContainerBlock) {
      return renderRangeCaretOpt(editor, nextCaretPosition.toRange(), false)
    }
    return Optional.none()
  }
  const moveVertically = (editor, direction, range, isBefore, isAfter, isElement) => {
    const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range)
    const caretClientRect = last$2(caretPosition.getClientRects())
    const forwards = direction === VDirection.Down
    const root = editor.getBody()
    if (!caretClientRect) {
      return Optional.none()
    }
    if (isCefAtEdgeSelected(editor)) {
      const caretPosition = forwards ? CaretPosition.fromRangeEnd(range) : CaretPosition.fromRangeStart(range)
      const getClosestFn = !forwards ? getClosestPositionAbove : getClosestPositionBelow
      return getClosestFn(root, caretPosition)
        .orThunk(() => Optional.from(caretPosition))
        .map((pos) => pos.toRange())
    }
    const walkerFn = forwards ? downUntil : upUntil
    const linePositions = walkerFn(root, isAboveLine(1), caretPosition)
    const nextLinePositions = filter$6(linePositions, isLine(1))
    const clientX = caretClientRect.left
    const nextLineRect = findClosestClientRect(nextLinePositions, clientX)
    if (nextLineRect && isElement(nextLineRect.node)) {
      const dist1 = Math.abs(clientX - nextLineRect.left)
      const dist2 = Math.abs(clientX - nextLineRect.right)
      return showCaret(direction, editor, nextLineRect.node, dist1 < dist2, false)
    }
    let currentNode
    if (isBefore(caretPosition)) {
      currentNode = caretPosition.getNode()
    } else if (isAfter(caretPosition)) {
      currentNode = caretPosition.getNode(true)
    } else {
      currentNode = getSelectedNode(range)
    }
    if (currentNode) {
      const caretPositions = positionsUntil(direction, root, isAboveLine(1), currentNode)
      let closestNextLineRect = findClosestClientRect(filter$6(caretPositions, isLine(1)), clientX)
      if (closestNextLineRect) {
        return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false)
      }
      closestNextLineRect = last$2(filter$6(caretPositions, isLine(0)))
      if (closestNextLineRect) {
        return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false)
      }
    }
    if (nextLinePositions.length === 0) {
      return getLineEndPoint(editor, forwards)
        .filter(forwards ? isAfter : isBefore)
        .map((pos) => renderRangeCaret(editor, pos.toRange(), false))
    }
    return Optional.none()
  }
  const getLineEndPoint = (editor, forward) => {
    const rng = editor.selection.getRng()
    const from = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng)
    const host = getEditingHost(from.container(), editor.getBody())
    if (forward) {
      const lineInfo = getPositionsUntilNextLine(host, from)
      return last$3(lineInfo.positions)
    } else {
      const lineInfo = getPositionsUntilPreviousLine(host, from)
      return head(lineInfo.positions)
    }
  }
  const moveToLineEndPoint$3 = (editor, forward, isElementPosition) =>
    getLineEndPoint(editor, forward)
      .filter(isElementPosition)
      .exists((pos) => {
        editor.selection.setRng(pos.toRange())
        return true
      })

  const setCaretPosition = (editor, pos) => {
    const rng = editor.dom.createRng()
    rng.setStart(pos.container(), pos.offset())
    rng.setEnd(pos.container(), pos.offset())
    editor.selection.setRng(rng)
  }
  const setSelected = (state, elm) => {
    if (state) {
      elm.setAttribute('data-mce-selected', 'inline-boundary')
    } else {
      elm.removeAttribute('data-mce-selected')
    }
  }
  const renderCaretLocation = (editor, caret, location) =>
    renderCaret(caret, location).map((pos) => {
      setCaretPosition(editor, pos)
      return location
    })
  const getPositionFromRange = (range, root, forward) => {
    const start = CaretPosition.fromRangeStart(range)
    if (range.collapsed) {
      return start
    } else {
      const end = CaretPosition.fromRangeEnd(range)
      return forward ? prevPosition(root, end).getOr(end) : nextPosition(root, start).getOr(start)
    }
  }
  const findLocation = (editor, caret, forward) => {
    const rootNode = editor.getBody()
    const from = getPositionFromRange(editor.selection.getRng(), rootNode, forward)
    const isInlineTarget$1 = curry(isInlineTarget, editor)
    const location = findLocation$1(forward, isInlineTarget$1, rootNode, from)
    return location.bind((location) => renderCaretLocation(editor, caret, location))
  }
  const toggleInlines = (isInlineTarget, dom, elms) => {
    const inlineBoundaries = map$3(
      descendants(SugarElement.fromDom(dom.getRoot()), '*[data-mce-selected="inline-boundary"]'),
      (e) => e.dom
    )
    const selectedInlines = filter$6(inlineBoundaries, isInlineTarget)
    const targetInlines = filter$6(elms, isInlineTarget)
    each$f(difference(selectedInlines, targetInlines), curry(setSelected, false))
    each$f(difference(targetInlines, selectedInlines), curry(setSelected, true))
  }
  const safeRemoveCaretContainer = (editor, caret) => {
    if (editor.selection.isCollapsed() && editor.composing !== true && caret.get()) {
      const pos = CaretPosition.fromRangeStart(editor.selection.getRng())
      if (CaretPosition.isTextPosition(pos) && isAtZwsp(pos) === false) {
        setCaretPosition(editor, removeAndReposition(caret.get(), pos))
        caret.set(null)
      }
    }
  }
  const renderInsideInlineCaret = (isInlineTarget, editor, caret, elms) => {
    if (editor.selection.isCollapsed()) {
      const inlines = filter$6(elms, isInlineTarget)
      each$f(inlines, (_inline) => {
        const pos = CaretPosition.fromRangeStart(editor.selection.getRng())
        readLocation(isInlineTarget, editor.getBody(), pos).bind((location) =>
          renderCaretLocation(editor, caret, location)
        )
      })
    }
  }
  const move$2 = (editor, caret, forward) =>
    isInlineBoundariesEnabled(editor) ? findLocation(editor, caret, forward).isSome() : false
  const moveWord = (forward, editor, _caret) =>
    isInlineBoundariesEnabled(editor) ? moveByWord(forward, editor) : false
  const setupSelectedState = (editor) => {
    const caret = Cell(null)
    const isInlineTarget$1 = curry(isInlineTarget, editor)
    editor.on('NodeChange', (e) => {
      if (isInlineBoundariesEnabled(editor)) {
        toggleInlines(isInlineTarget$1, editor.dom, e.parents)
        safeRemoveCaretContainer(editor, caret)
        renderInsideInlineCaret(isInlineTarget$1, editor, caret, e.parents)
      }
    })
    return caret
  }
  const moveNextWord = curry(moveWord, true)
  const movePrevWord = curry(moveWord, false)
  const moveToLineEndPoint$2 = (editor, forward, caret) => {
    if (isInlineBoundariesEnabled(editor)) {
      const linePoint = getLineEndPoint(editor, forward).getOrThunk(() => {
        const rng = editor.selection.getRng()
        return forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng)
      })
      return readLocation(curry(isInlineTarget, editor), editor.getBody(), linePoint).exists((loc) => {
        const outsideLoc = outside(loc)
        return renderCaret(caret, outsideLoc).exists((pos) => {
          setCaretPosition(editor, pos)
          return true
        })
      })
    } else {
      return false
    }
  }

  const rangeFromPositions = (from, to) => {
    const range = document.createRange()
    range.setStart(from.container(), from.offset())
    range.setEnd(to.container(), to.offset())
    return range
  }
  const hasOnlyTwoOrLessPositionsLeft = (elm) =>
    lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
      const normalizedFirstPos = normalizePosition(true, firstPos)
      const normalizedLastPos = normalizePosition(false, lastPos)
      return nextPosition(elm, normalizedFirstPos).forall((pos) => pos.isEqual(normalizedLastPos))
    }).getOr(true)
  const setCaretLocation = (editor, caret) => (location) =>
    renderCaret(caret, location).map((pos) => () => setCaretPosition(editor, pos))
  const deleteFromTo = (editor, caret, from, to) => {
    const rootNode = editor.getBody()
    const isInlineTarget$1 = curry(isInlineTarget, editor)
    editor.undoManager.ignore(() => {
      editor.selection.setRng(rangeFromPositions(from, to))
      execDeleteCommand(editor)
      readLocation(isInlineTarget$1, rootNode, CaretPosition.fromRangeStart(editor.selection.getRng()))
        .map(inside)
        .bind(setCaretLocation(editor, caret))
        .each(call)
    })
    editor.nodeChanged()
  }
  const rescope = (rootNode, node) => {
    const parentBlock = getParentBlock$3(node, rootNode)
    return parentBlock ? parentBlock : rootNode
  }
  const backspaceDeleteCollapsed = (editor, caret, forward, from) => {
    const rootNode = rescope(editor.getBody(), from.container())
    const isInlineTarget$1 = curry(isInlineTarget, editor)
    const fromLocation = readLocation(isInlineTarget$1, rootNode, from)
    const location = fromLocation.bind((location) => {
      if (forward) {
        return location.fold(
          constant(Optional.some(inside(location))),
          Optional.none,
          constant(Optional.some(outside(location))),
          Optional.none
        )
      } else {
        return location.fold(
          Optional.none,
          constant(Optional.some(outside(location))),
          Optional.none,
          constant(Optional.some(inside(location)))
        )
      }
    })
    return location.map(setCaretLocation(editor, caret)).getOrThunk(() => {
      const toPosition = navigate(forward, rootNode, from)
      const toLocation = toPosition.bind((pos) => readLocation(isInlineTarget$1, rootNode, pos))
      return lift2(fromLocation, toLocation, () =>
        findRootInline(isInlineTarget$1, rootNode, from).bind((elm) => {
          if (hasOnlyTwoOrLessPositionsLeft(elm)) {
            return Optional.some(() => {
              deleteElement$2(editor, forward, SugarElement.fromDom(elm))
            })
          } else {
            return Optional.none()
          }
        })
      ).getOrThunk(() =>
        toLocation.bind(() =>
          toPosition.map((to) => {
            return () => {
              if (forward) {
                deleteFromTo(editor, caret, from, to)
              } else {
                deleteFromTo(editor, caret, to, from)
              }
            }
          })
        )
      )
    })
  }
  const backspaceDelete$3 = (editor, caret, forward) => {
    if (editor.selection.isCollapsed() && isInlineBoundariesEnabled(editor)) {
      const from = CaretPosition.fromRangeStart(editor.selection.getRng())
      return backspaceDeleteCollapsed(editor, caret, forward, from)
    }
    return Optional.none()
  }

  const getParentInlines = (rootElm, startElm) => {
    const parents = parentsAndSelf(startElm, rootElm)
    return findIndex$2(parents, isBlock$2).fold(constant(parents), (index) => parents.slice(0, index))
  }
  const hasOnlyOneChild = (elm) => childNodesCount(elm) === 1
  const deleteLastPosition = (forward, editor, target, parentInlines) => {
    const isFormatElement$1 = curry(isFormatElement, editor)
    const formatNodes = map$3(filter$6(parentInlines, isFormatElement$1), (elm) => elm.dom)
    if (formatNodes.length === 0) {
      deleteElement$2(editor, forward, target)
    } else {
      const pos = replaceWithCaretFormat(target.dom, formatNodes)
      editor.selection.setRng(pos.toRange())
    }
  }
  const deleteCaret$1 = (editor, forward) => {
    const rootElm = SugarElement.fromDom(editor.getBody())
    const startElm = SugarElement.fromDom(editor.selection.getStart())
    const parentInlines = filter$6(getParentInlines(rootElm, startElm), hasOnlyOneChild)
    return last$3(parentInlines).bind((target) => {
      const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng())
      if (willDeleteLastPositionInElement(forward, fromPos, target.dom) && !isEmptyCaretFormatElement(target)) {
        return Optional.some(() => deleteLastPosition(forward, editor, target, parentInlines))
      } else {
        return Optional.none()
      }
    })
  }
  const backspaceDelete$2 = (editor, forward) =>
    editor.selection.isCollapsed() ? deleteCaret$1(editor, forward) : Optional.none()

  const deleteElement = (editor, forward, element) => {
    return Optional.some(() => {
      editor._selectionOverrides.hideFakeCaret()
      deleteElement$2(editor, forward, SugarElement.fromDom(element))
    })
  }
  const deleteCaret = (editor, forward) => {
    const isNearMedia = forward ? isBeforeMedia : isAfterMedia
    const direction = forward ? HDirection.Forwards : HDirection.Backwards
    const fromPos = getNormalizedRangeEndPoint(direction, editor.getBody(), editor.selection.getRng())
    if (isNearMedia(fromPos)) {
      return deleteElement(editor, forward, fromPos.getNode(!forward))
    } else {
      return Optional.from(normalizePosition(forward, fromPos))
        .filter((pos) => isNearMedia(pos) && isMoveInsideSameBlock(fromPos, pos))
        .map((pos) => () => deleteElement(editor, forward, pos.getNode(!forward)))
    }
  }
  const deleteRange = (editor, forward) => {
    const selectedNode = editor.selection.getNode()
    return isMedia$2(selectedNode) ? deleteElement(editor, forward, selectedNode) : Optional.none()
  }
  const backspaceDelete$1 = (editor, forward) =>
    editor.selection.isCollapsed() ? deleteCaret(editor, forward) : deleteRange(editor, forward)

  const isEditable$1 = (target) =>
    closest$4(target, (elm) => isContentEditableTrue$5(elm.dom) || isContentEditableFalse$b(elm.dom)).exists((elm) =>
      isContentEditableTrue$5(elm.dom)
    )
  const parseIndentValue = (value) => {
    const number = parseInt(value, 10)
    return isNaN(number) ? 0 : number
  }
  const getIndentStyleName = (useMargin, element) => {
    const indentStyleName = useMargin || isTable$2(element) ? 'margin' : 'padding'
    const suffix = get$7(element, 'direction') === 'rtl' ? '-right' : '-left'
    return indentStyleName + suffix
  }
  const indentElement = (dom, command, useMargin, value, unit, element) => {
    const indentStyleName = getIndentStyleName(useMargin, SugarElement.fromDom(element))
    if (command === 'outdent') {
      const styleValue = Math.max(0, parseIndentValue(element.style[indentStyleName]) - value)
      dom.setStyle(element, indentStyleName, styleValue ? styleValue + unit : '')
    } else {
      const styleValue = parseIndentValue(element.style[indentStyleName]) + value + unit
      dom.setStyle(element, indentStyleName, styleValue)
    }
  }
  const validateBlocks = (editor, blocks) =>
    forall(blocks, (block) => {
      const indentStyleName = getIndentStyleName(shouldIndentUseMargin(editor), block)
      const intentValue = getRaw$1(block, indentStyleName).map(parseIndentValue).getOr(0)
      const contentEditable = editor.dom.getContentEditable(block.dom)
      return contentEditable !== 'false' && intentValue > 0
    })
  const canOutdent = (editor) => {
    const blocks = getBlocksToIndent(editor)
    return !editor.mode.isReadOnly() && (blocks.length > 1 || validateBlocks(editor, blocks))
  }
  const isListComponent = (el) => isList(el) || isListItem(el)
  const parentIsListComponent = (el) => parent(el).exists(isListComponent)
  const getBlocksToIndent = (editor) =>
    filter$6(
      fromDom$1(editor.selection.getSelectedBlocks()),
      (el) => !isListComponent(el) && !parentIsListComponent(el) && isEditable$1(el)
    )
  const handle = (editor, command) => {
    const { dom } = editor
    const indentation = getIndentation(editor)
    const indentUnit = /[a-z%]+$/i.exec(indentation)[0]
    const indentValue = parseInt(indentation, 10)
    const useMargin = shouldIndentUseMargin(editor)
    each$f(getBlocksToIndent(editor), (block) => {
      indentElement(dom, command, useMargin, indentValue, indentUnit, block.dom)
    })
  }
  const indent = (editor) => handle(editor, 'indent')
  const outdent = (editor) => handle(editor, 'outdent')

  const backspaceDelete = (editor) => {
    if (editor.selection.isCollapsed() && canOutdent(editor)) {
      const dom = editor.dom
      const rng = editor.selection.getRng()
      const pos = CaretPosition.fromRangeStart(rng)
      const block = dom.getParent(rng.startContainer, dom.isBlock)
      if (block !== null && isAtStartOfBlock(SugarElement.fromDom(block), pos)) {
        return Optional.some(() => outdent(editor))
      }
    }
    return Optional.none()
  }

  const findAction = (editor, caret, forward) =>
    findMap(
      [
        backspaceDelete,
        backspaceDelete$5,
        backspaceDelete$6,
        (editor, forward) => backspaceDelete$3(editor, caret, forward),
        backspaceDelete$8,
        backspaceDelete$9,
        backspaceDelete$4,
        backspaceDelete$1,
        backspaceDelete$7,
        backspaceDelete$2,
      ],
      (item) => item(editor, forward)
    )
  const deleteCommand = (editor, caret) => {
    const result = findAction(editor, caret, false)
    result.fold(() => {
      execDeleteCommand(editor)
      paddEmptyBody(editor)
    }, call)
  }
  const forwardDeleteCommand = (editor, caret) => {
    const result = findAction(editor, caret, true)
    result.fold(() => execForwardDeleteCommand(editor), call)
  }
  const setup$p = (editor, caret) => {
    editor.addCommand('delete', () => {
      deleteCommand(editor, caret)
    })
    editor.addCommand('forwardDelete', () => {
      forwardDeleteCommand(editor, caret)
    })
  }

  const SIGNIFICANT_MOVE = 5
  const LONGPRESS_DELAY = 400
  const getTouch = (event) => {
    if (event.touches === undefined || event.touches.length !== 1) {
      return Optional.none()
    }
    return Optional.some(event.touches[0])
  }
  const isFarEnough = (touch, data) => {
    const distX = Math.abs(touch.clientX - data.x)
    const distY = Math.abs(touch.clientY - data.y)
    return distX > SIGNIFICANT_MOVE || distY > SIGNIFICANT_MOVE
  }
  const setup$o = (editor) => {
    const startData = value$2()
    const longpressFired = Cell(false)
    const debounceLongpress = last$1((e) => {
      editor.dispatch('longpress', {
        ...e,
        type: 'longpress',
      })
      longpressFired.set(true)
    }, LONGPRESS_DELAY)
    editor.on(
      'touchstart',
      (e) => {
        getTouch(e).each((touch) => {
          debounceLongpress.cancel()
          const data = {
            x: touch.clientX,
            y: touch.clientY,
            target: e.target,
          }
          debounceLongpress.throttle(e)
          longpressFired.set(false)
          startData.set(data)
        })
      },
      true
    )
    editor.on(
      'touchmove',
      (e) => {
        debounceLongpress.cancel()
        getTouch(e).each((touch) => {
          startData.on((data) => {
            if (isFarEnough(touch, data)) {
              startData.clear()
              longpressFired.set(false)
              editor.dispatch('longpresscancel')
            }
          })
        })
      },
      true
    )
    editor.on(
      'touchend touchcancel',
      (e) => {
        debounceLongpress.cancel()
        if (e.type === 'touchcancel') {
          return
        }
        startData
          .get()
          .filter((data) => data.target.isEqualNode(e.target))
          .each(() => {
            if (longpressFired.get()) {
              e.preventDefault()
            } else {
              editor.dispatch('tap', {
                ...e,
                type: 'tap',
              })
            }
          })
      },
      true
    )
  }

  const isBlockElement = (blockElements, node) => has$2(blockElements, node.nodeName)
  const isValidTarget = (blockElements, node) => {
    if (isText$9(node)) {
      return true
    } else if (isElement$6(node)) {
      return !isBlockElement(blockElements, node) && !isBookmarkNode$1(node)
    } else {
      return false
    }
  }
  const hasBlockParent = (blockElements, root, node) => {
    return exists(parents(SugarElement.fromDom(node), SugarElement.fromDom(root)), (elm) => {
      return isBlockElement(blockElements, elm.dom)
    })
  }
  const shouldRemoveTextNode = (blockElements, node) => {
    if (isText$9(node)) {
      if (node.nodeValue.length === 0) {
        return true
      } else if (
        /^\s+$/.test(node.nodeValue) &&
        (!node.nextSibling || isBlockElement(blockElements, node.nextSibling))
      ) {
        return true
      }
    }
    return false
  }
  const createRootBlock = (editor) => editor.dom.create(getForcedRootBlock(editor), getForcedRootBlockAttrs(editor))
  const addRootBlocks = (editor) => {
    const dom = editor.dom,
      selection = editor.selection
    const schema = editor.schema,
      blockElements = schema.getBlockElements()
    let node = selection.getStart()
    const rootNode = editor.getBody()
    let rootBlockNode, tempNode, wrapped
    const forcedRootBlock = getForcedRootBlock(editor)
    if (!node || !isElement$6(node)) {
      return
    }
    const rootNodeName = rootNode.nodeName.toLowerCase()
    if (
      !schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase()) ||
      hasBlockParent(blockElements, rootNode, node)
    ) {
      return
    }
    const rng = selection.getRng()
    const startContainer = rng.startContainer
    const startOffset = rng.startOffset
    const endContainer = rng.endContainer
    const endOffset = rng.endOffset
    const restoreSelection = hasFocus(editor)
    node = rootNode.firstChild
    while (node) {
      if (isValidTarget(blockElements, node)) {
        if (shouldRemoveTextNode(blockElements, node)) {
          tempNode = node
          node = node.nextSibling
          dom.remove(tempNode)
          continue
        }
        if (!rootBlockNode) {
          rootBlockNode = createRootBlock(editor)
          node.parentNode.insertBefore(rootBlockNode, node)
          wrapped = true
        }
        tempNode = node
        node = node.nextSibling
        rootBlockNode.appendChild(tempNode)
      } else {
        rootBlockNode = null
        node = node.nextSibling
      }
    }
    if (wrapped && restoreSelection) {
      rng.setStart(startContainer, startOffset)
      rng.setEnd(endContainer, endOffset)
      selection.setRng(rng)
      editor.nodeChanged()
    }
  }
  const insertEmptyLine = (editor, root, insertBlock) => {
    const block = SugarElement.fromDom(createRootBlock(editor))
    const br = createPaddingBr()
    append$1(block, br)
    insertBlock(root, block)
    const rng = document.createRange()
    rng.setStartBefore(br.dom)
    rng.setEndBefore(br.dom)
    return rng
  }
  const setup$n = (editor) => {
    editor.on('NodeChange', curry(addRootBlocks, editor))
  }

  const hasClass = (checkClassName) => (node) => (' ' + node.attr('class') + ' ').indexOf(checkClassName) !== -1
  const replaceMatchWithSpan = (editor, content, cls) => {
    return function (match) {
      const args = arguments,
        index = args[args.length - 2]
      const prevChar = index > 0 ? content.charAt(index - 1) : ''
      if (prevChar === '"') {
        return match
      }
      if (prevChar === '>') {
        const findStartTagIndex = content.lastIndexOf('<', index)
        if (findStartTagIndex !== -1) {
          const tagHtml = content.substring(findStartTagIndex, index)
          if (tagHtml.indexOf('contenteditable="false"') !== -1) {
            return match
          }
        }
      }
      return (
        '<span class="' +
        cls +
        '" data-mce-content="' +
        editor.dom.encode(args[0]) +
        '">' +
        editor.dom.encode(typeof args[1] === 'string' ? args[1] : args[0]) +
        '</span>'
      )
    }
  }
  const convertRegExpsToNonEditable = (editor, nonEditableRegExps, e) => {
    let i = nonEditableRegExps.length,
      content = e.content
    if (e.format === 'raw') {
      return
    }
    while (i--) {
      content = content.replace(
        nonEditableRegExps[i],
        replaceMatchWithSpan(editor, content, getNonEditableClass(editor))
      )
    }
    e.content = content
  }
  const setup$m = (editor) => {
    const contentEditableAttrName = 'contenteditable'
    const editClass = ' ' + Tools.trim(getEditableClass(editor)) + ' '
    const nonEditClass = ' ' + Tools.trim(getNonEditableClass(editor)) + ' '
    const hasEditClass = hasClass(editClass)
    const hasNonEditClass = hasClass(nonEditClass)
    const nonEditableRegExps = getNonEditableRegExps(editor)
    if (nonEditableRegExps.length > 0) {
      editor.on('BeforeSetContent', (e) => {
        convertRegExpsToNonEditable(editor, nonEditableRegExps, e)
      })
    }
    editor.parser.addAttributeFilter('class', (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        if (hasEditClass(node)) {
          node.attr(contentEditableAttrName, 'true')
        } else if (hasNonEditClass(node)) {
          node.attr(contentEditableAttrName, 'false')
        }
      }
    })
    editor.serializer.addAttributeFilter(contentEditableAttrName, (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        if (!hasEditClass(node) && !hasNonEditClass(node)) {
          continue
        }
        if (nonEditableRegExps.length > 0 && node.attr('data-mce-content')) {
          node.name = '#text'
          node.type = 3
          node.raw = true
          node.value = node.attr('data-mce-content')
        } else {
          node.attr(contentEditableAttrName, null)
        }
      }
    })
  }

  const findBlockCaretContainer = (editor) =>
    descendant(SugarElement.fromDom(editor.getBody()), '*[data-mce-caret]')
      .map((elm) => elm.dom)
      .getOrNull()
  const showBlockCaretContainer = (editor, blockCaretContainer) => {
    if (blockCaretContainer.hasAttribute('data-mce-caret')) {
      showCaretContainerBlock(blockCaretContainer)
      editor.selection.setRng(editor.selection.getRng())
      editor.selection.scrollIntoView(blockCaretContainer)
    }
  }
  const handleBlockContainer = (editor, e) => {
    const blockCaretContainer = findBlockCaretContainer(editor)
    if (!blockCaretContainer) {
      return
    }
    if (e.type === 'compositionstart') {
      e.preventDefault()
      e.stopPropagation()
      showBlockCaretContainer(editor, blockCaretContainer)
      return
    }
    if (hasContent(blockCaretContainer)) {
      showBlockCaretContainer(editor, blockCaretContainer)
      editor.undoManager.add()
    }
  }
  const setup$l = (editor) => {
    editor.on('keyup compositionstart', curry(handleBlockContainer, editor))
  }

  const isContentEditableFalse$3 = isContentEditableFalse$b
  const moveToCeFalseHorizontally = (direction, editor, range) =>
    moveHorizontally(
      editor,
      direction,
      range,
      isBeforeContentEditableFalse,
      isAfterContentEditableFalse,
      isContentEditableFalse$3
    )
  const moveToCeFalseVertically = (direction, editor, range) => {
    const isBefore = (caretPosition) => isBeforeContentEditableFalse(caretPosition) || isBeforeTable(caretPosition)
    const isAfter = (caretPosition) => isAfterContentEditableFalse(caretPosition) || isAfterTable(caretPosition)
    return moveVertically(editor, direction, range, isBefore, isAfter, isContentEditableFalse$3)
  }
  const createTextBlock = (editor) => {
    const textBlock = editor.dom.create(getForcedRootBlock(editor))
    textBlock.innerHTML = '<br data-mce-bogus="1">'
    return textBlock
  }
  const exitPreBlock = (editor, direction, range) => {
    const caretWalker = CaretWalker(editor.getBody())
    const getVisualCaretPosition$1 = curry(
      getVisualCaretPosition,
      direction === 1 ? caretWalker.next : caretWalker.prev
    )
    if (range.collapsed) {
      const pre = editor.dom.getParent(range.startContainer, 'PRE')
      if (!pre) {
        return
      }
      const caretPos = getVisualCaretPosition$1(CaretPosition.fromRangeStart(range))
      if (!caretPos) {
        const newBlock = SugarElement.fromDom(createTextBlock(editor))
        if (direction === 1) {
          after$4(SugarElement.fromDom(pre), newBlock)
        } else {
          before$3(SugarElement.fromDom(pre), newBlock)
        }
        editor.selection.select(newBlock.dom, true)
        editor.selection.collapse()
      }
    }
  }
  const getHorizontalRange = (editor, forward) => {
    const direction = forward ? HDirection.Forwards : HDirection.Backwards
    const range = editor.selection.getRng()
    return moveToCeFalseHorizontally(direction, editor, range).orThunk(() => {
      exitPreBlock(editor, direction, range)
      return Optional.none()
    })
  }
  const getVerticalRange = (editor, down) => {
    const direction = down ? 1 : -1
    const range = editor.selection.getRng()
    return moveToCeFalseVertically(direction, editor, range).orThunk(() => {
      exitPreBlock(editor, direction, range)
      return Optional.none()
    })
  }
  const moveH$2 = (editor, forward) =>
    getHorizontalRange(editor, forward).exists((newRange) => {
      moveToRange(editor, newRange)
      return true
    })
  const moveV$3 = (editor, down) =>
    getVerticalRange(editor, down).exists((newRange) => {
      moveToRange(editor, newRange)
      return true
    })
  const moveToLineEndPoint$1 = (editor, forward) => {
    const isCefPosition = forward ? isAfterContentEditableFalse : isBeforeContentEditableFalse
    return moveToLineEndPoint$3(editor, forward, isCefPosition)
  }
  const selectToEndPoint = (editor, forward) =>
    getEdgeCefPosition(editor, !forward)
      .map((pos) => {
        const rng = pos.toRange()
        const curRng = editor.selection.getRng()
        if (forward) {
          rng.setStart(curRng.startContainer, curRng.startOffset)
        } else {
          rng.setEnd(curRng.endContainer, curRng.endOffset)
        }
        return rng
      })
      .exists((rng) => {
        moveToRange(editor, rng)
        return true
      })

  const isTarget = (node) => contains$2(['figcaption'], name(node))
  const getClosestTargetBlock = (pos, root) => {
    const isRoot = curry(eq, root)
    return closest$4(SugarElement.fromDom(pos.container()), isBlock$2, isRoot).filter(isTarget)
  }
  const isAtFirstOrLastLine = (root, forward, pos) =>
    forward ? isAtLastLine(root.dom, pos) : isAtFirstLine(root.dom, pos)
  const moveCaretToNewEmptyLine = (editor, forward) => {
    const root = SugarElement.fromDom(editor.getBody())
    const pos = CaretPosition.fromRangeStart(editor.selection.getRng())
    return getClosestTargetBlock(pos, root).exists(() => {
      if (isAtFirstOrLastLine(root, forward, pos)) {
        const insertFn = forward ? append$1 : prepend
        const rng = insertEmptyLine(editor, root, insertFn)
        editor.selection.setRng(rng)
        return true
      } else {
        return false
      }
    })
  }
  const moveV$2 = (editor, forward) => {
    if (editor.selection.isCollapsed()) {
      return moveCaretToNewEmptyLine(editor, forward)
    } else {
      return false
    }
  }

  const baseKeyPattern = {
    shiftKey: false,
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    keyCode: 0,
  }
  const defaultPatterns = (patterns) =>
    map$3(patterns, (pattern) => ({
      ...baseKeyPattern,
      action: noop,
      ...pattern,
    }))
  const defaultDelayedPatterns = (patterns) =>
    map$3(patterns, (pattern) => ({
      ...baseKeyPattern,
      action: () => Optional.none(),
      ...pattern,
    }))
  const matchesEvent = (pattern, evt) =>
    evt.keyCode === pattern.keyCode &&
    evt.shiftKey === pattern.shiftKey &&
    evt.altKey === pattern.altKey &&
    evt.ctrlKey === pattern.ctrlKey &&
    evt.metaKey === pattern.metaKey
  const match$1 = (patterns, evt) =>
    bind$3(defaultPatterns(patterns), (pattern) => (matchesEvent(pattern, evt) ? [pattern] : []))
  const matchDelayed = (patterns, evt) =>
    bind$3(defaultDelayedPatterns(patterns), (pattern) => (matchesEvent(pattern, evt) ? [pattern] : []))
  const action =
    (f, ...x) =>
    () =>
      f.apply(null, x)
  const execute = (patterns, evt) => find$2(match$1(patterns, evt), (pattern) => pattern.action())
  const executeWithDelayedAction = (patterns, evt) =>
    findMap(matchDelayed(patterns, evt), (pattern) => pattern.action())

  const moveH$1 = (editor, forward) => {
    const direction = forward ? HDirection.Forwards : HDirection.Backwards
    const range = editor.selection.getRng()
    return moveHorizontally(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
      moveToRange(editor, newRange)
      return true
    })
  }
  const moveV$1 = (editor, down) => {
    const direction = down ? 1 : -1
    const range = editor.selection.getRng()
    return moveVertically(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
      moveToRange(editor, newRange)
      return true
    })
  }
  const moveToLineEndPoint = (editor, forward) => {
    const isNearMedia = forward ? isAfterMedia : isBeforeMedia
    return moveToLineEndPoint$3(editor, forward, isNearMedia)
  }

  const adt = Adt.generate([
    { none: ['current'] },
    { first: ['current'] },
    {
      middle: ['current', 'target'],
    },
    { last: ['current'] },
  ])
  const none = (current) => adt.none(current)
  const CellLocation = {
    ...adt,
    none,
  }

  const firstLayer = (scope, selector) => {
    return filterFirstLayer(scope, selector, always)
  }
  const filterFirstLayer = (scope, selector, predicate) => {
    return bind$3(children(scope), (x) => {
      if (is$1(x, selector)) {
        return predicate(x) ? [x] : []
      } else {
        return filterFirstLayer(x, selector, predicate)
      }
    })
  }

  const lookup$1 = (tags, element, isRoot = never) => {
    if (isRoot(element)) {
      return Optional.none()
    }
    if (contains$2(tags, name(element))) {
      return Optional.some(element)
    }
    const isRootOrUpperTable = (elm) => is$1(elm, 'table') || isRoot(elm)
    return ancestor$2(element, tags.join(','), isRootOrUpperTable)
  }
  const cell = (element, isRoot) => lookup$1(['td', 'th'], element, isRoot)
  const cells = (ancestor) => firstLayer(ancestor, 'th,td')
  const table = (element, isRoot) => closest$3(element, 'table', isRoot)

  const walk = (all, current, index, direction, isEligible = always) => {
    const forwards = direction === 1
    if (!forwards && index <= 0) {
      return CellLocation.first(all[0])
    } else if (forwards && index >= all.length - 1) {
      return CellLocation.last(all[all.length - 1])
    } else {
      const newIndex = index + direction
      const elem = all[newIndex]
      return isEligible(elem) ? CellLocation.middle(current, elem) : walk(all, current, newIndex, direction, isEligible)
    }
  }
  const detect = (current, isRoot) => {
    return table(current, isRoot).bind((table) => {
      const all = cells(table)
      const index = findIndex$2(all, (x) => eq(current, x))
      return index.map((index) => ({
        index,
        all,
      }))
    })
  }
  const next = (current, isEligible, isRoot) => {
    const detection = detect(current, isRoot)
    return detection.fold(
      () => {
        return CellLocation.none(current)
      },
      (info) => {
        return walk(info.all, current, info.index, 1, isEligible)
      }
    )
  }
  const prev = (current, isEligible, isRoot) => {
    const detection = detect(current, isRoot)
    return detection.fold(
      () => {
        return CellLocation.none()
      },
      (info) => {
        return walk(info.all, current, info.index, -1, isEligible)
      }
    )
  }

  const closest = (target) => closest$3(target, '[contenteditable]')
  const isEditable = (element, assumeEditable = false) => {
    if (inBody(element)) {
      return element.dom.isContentEditable
    } else {
      return closest(element).fold(constant(assumeEditable), (editable) => getRaw(editable) === 'true')
    }
  }
  const getRaw = (element) => element.dom.contentEditable

  const deflate = (rect, delta) => ({
    left: rect.left - delta,
    top: rect.top - delta,
    right: rect.right + delta * 2,
    bottom: rect.bottom + delta * 2,
    width: rect.width + delta,
    height: rect.height + delta,
  })
  const getCorners = (getYAxisValue, tds) =>
    bind$3(tds, (td) => {
      const rect = deflate(clone$1(td.getBoundingClientRect()), -1)
      return [
        {
          x: rect.left,
          y: getYAxisValue(rect),
          cell: td,
        },
        {
          x: rect.right,
          y: getYAxisValue(rect),
          cell: td,
        },
      ]
    })
  const findClosestCorner = (corners, x, y) =>
    foldl(
      corners,
      (acc, newCorner) =>
        acc.fold(
          () => Optional.some(newCorner),
          (oldCorner) => {
            const oldDist = Math.sqrt(Math.abs(oldCorner.x - x) + Math.abs(oldCorner.y - y))
            const newDist = Math.sqrt(Math.abs(newCorner.x - x) + Math.abs(newCorner.y - y))
            return Optional.some(newDist < oldDist ? newCorner : oldCorner)
          }
        ),
      Optional.none()
    )
  const getClosestCell = (getYAxisValue, isTargetCorner, table, x, y) => {
    const cells = descendants(SugarElement.fromDom(table), 'td,th,caption').map((e) => e.dom)
    const corners = filter$6(getCorners(getYAxisValue, cells), (corner) => isTargetCorner(corner, y))
    return findClosestCorner(corners, x, y).map((corner) => corner.cell)
  }
  const getBottomValue = (rect) => rect.bottom
  const getTopValue = (rect) => rect.top
  const isAbove = (corner, y) => corner.y < y
  const isBelow = (corner, y) => corner.y > y
  const getClosestCellAbove = curry(getClosestCell, getBottomValue, isAbove)
  const getClosestCellBelow = curry(getClosestCell, getTopValue, isBelow)
  const findClosestPositionInAboveCell = (table, pos) =>
    head(pos.getClientRects())
      .bind((rect) => getClosestCellAbove(table, rect.left, rect.top))
      .bind((cell) => findClosestHorizontalPosition(getLastLinePositions(cell), pos))
  const findClosestPositionInBelowCell = (table, pos) =>
    last$3(pos.getClientRects())
      .bind((rect) => getClosestCellBelow(table, rect.left, rect.top))
      .bind((cell) => findClosestHorizontalPosition(getFirstLinePositions(cell), pos))

  const hasNextBreak = (getPositionsUntil, scope, lineInfo) =>
    lineInfo.breakAt.exists((breakPos) => getPositionsUntil(scope, breakPos).breakAt.isSome())
  const startsWithWrapBreak = (lineInfo) => lineInfo.breakType === BreakType.Wrap && lineInfo.positions.length === 0
  const startsWithBrBreak = (lineInfo) => lineInfo.breakType === BreakType.Br && lineInfo.positions.length === 1
  const isAtTableCellLine = (getPositionsUntil, scope, pos) => {
    const lineInfo = getPositionsUntil(scope, pos)
    if (startsWithWrapBreak(lineInfo) || (!isBr$6(pos.getNode()) && startsWithBrBreak(lineInfo))) {
      return !hasNextBreak(getPositionsUntil, scope, lineInfo)
    } else {
      return lineInfo.breakAt.isNone()
    }
  }
  const isAtFirstTableCellLine = curry(isAtTableCellLine, getPositionsUntilPreviousLine)
  const isAtLastTableCellLine = curry(isAtTableCellLine, getPositionsUntilNextLine)
  const isCaretAtStartOrEndOfTable = (forward, rng, table) => {
    const caretPos = CaretPosition.fromRangeStart(rng)
    return positionIn(!forward, table).exists((pos) => pos.isEqual(caretPos))
  }
  const navigateHorizontally = (editor, forward, table, _td) => {
    const rng = editor.selection.getRng()
    const direction = forward ? 1 : -1
    if (isFakeCaretTableBrowser() && isCaretAtStartOrEndOfTable(forward, rng, table)) {
      showCaret(direction, editor, table, !forward, false).each((newRng) => {
        moveToRange(editor, newRng)
      })
      return true
    }
    return false
  }
  const getClosestAbovePosition = (root, table, start) =>
    findClosestPositionInAboveCell(table, start)
      .orThunk(() =>
        head(start.getClientRects()).bind((rect) =>
          findClosestHorizontalPositionFromPoint(getPositionsAbove(root, CaretPosition.before(table)), rect.left)
        )
      )
      .getOr(CaretPosition.before(table))
  const getClosestBelowPosition = (root, table, start) =>
    findClosestPositionInBelowCell(table, start)
      .orThunk(() =>
        head(start.getClientRects()).bind((rect) =>
          findClosestHorizontalPositionFromPoint(getPositionsBelow(root, CaretPosition.after(table)), rect.left)
        )
      )
      .getOr(CaretPosition.after(table))
  const getTable = (previous, pos) => {
    const node = pos.getNode(previous)
    return isTable$3(node) ? Optional.some(node) : Optional.none()
  }
  const renderBlock = (down, editor, table) => {
    editor.undoManager.transact(() => {
      const insertFn = down ? after$4 : before$3
      const rng = insertEmptyLine(editor, SugarElement.fromDom(table), insertFn)
      moveToRange(editor, rng)
    })
  }
  const moveCaret = (editor, down, pos) => {
    const table = down ? getTable(true, pos) : getTable(false, pos)
    const last = down === false
    table.fold(
      () => moveToRange(editor, pos.toRange()),
      (table) =>
        positionIn(last, editor.getBody())
          .filter((lastPos) => lastPos.isEqual(pos))
          .fold(
            () => moveToRange(editor, pos.toRange()),
            (_) => renderBlock(down, editor, table)
          )
    )
  }
  const navigateVertically = (editor, down, table, td) => {
    const rng = editor.selection.getRng()
    const pos = CaretPosition.fromRangeStart(rng)
    const root = editor.getBody()
    if (!down && isAtFirstTableCellLine(td, pos)) {
      const newPos = getClosestAbovePosition(root, table, pos)
      moveCaret(editor, down, newPos)
      return true
    } else if (down && isAtLastTableCellLine(td, pos)) {
      const newPos = getClosestBelowPosition(root, table, pos)
      moveCaret(editor, down, newPos)
      return true
    } else {
      return false
    }
  }
  const move$1 = (editor, forward, mover) =>
    Optional.from(editor.dom.getParent(editor.selection.getNode(), 'td,th'))
      .bind((td) => Optional.from(editor.dom.getParent(td, 'table')).map((table) => mover(editor, forward, table, td)))
      .getOr(false)
  const moveH = (editor, forward) => move$1(editor, forward, navigateHorizontally)
  const moveV = (editor, forward) => move$1(editor, forward, navigateVertically)
  const getCellFirstCursorPosition = (cell) => {
    const selection = SimSelection.exact(cell, 0, cell, 0)
    return toNative(selection)
  }
  const tabGo = (editor, isRoot, cell) => {
    return cell.fold(
      Optional.none,
      Optional.none,
      (_current, next) => {
        return first(next).map((cell) => {
          return getCellFirstCursorPosition(cell)
        })
      },
      (current) => {
        editor.execCommand('mceTableInsertRowAfter')
        return tabForward(editor, isRoot, current)
      }
    )
  }
  const tabForward = (editor, isRoot, cell) => tabGo(editor, isRoot, next(cell, isEditable))
  const tabBackward = (editor, isRoot, cell) => tabGo(editor, isRoot, prev(cell, isEditable))
  const handleTab = (editor, forward) => {
    const rootElements = ['table', 'li', 'dl']
    const body = SugarElement.fromDom(editor.getBody())
    const isRoot = (element) => {
      const name$1 = name(element)
      return eq(element, body) || contains$2(rootElements, name$1)
    }
    const rng = editor.selection.getRng()
    const container = SugarElement.fromDom(!forward ? rng.startContainer : rng.endContainer)
    return cell(container, isRoot)
      .map((cell) => {
        table(cell, isRoot).each((table) => {
          editor.model.table.clearSelectedCells(table.dom)
        })
        editor.selection.collapse(!forward)
        const navigation = !forward ? tabBackward : tabForward
        const rng = navigation(editor, isRoot, cell)
        rng.each((range) => {
          editor.selection.setRng(range)
        })
        return true
      })
      .getOr(false)
  }

  const executeKeydownOverride$4 = (editor, caret, evt) => {
    const isMac = Env.os.isMacOS() || Env.os.isiOS()
    execute(
      [
        {
          keyCode: VK.RIGHT,
          action: action(moveH$2, editor, true),
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH$2, editor, false),
        },
        {
          keyCode: VK.UP,
          action: action(moveV$3, editor, false),
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$3, editor, true),
        },
        ...(isMac
          ? [
              {
                keyCode: VK.UP,
                action: action(selectToEndPoint, editor, false),
                metaKey: true,
                shiftKey: true,
              },
              {
                keyCode: VK.DOWN,
                action: action(selectToEndPoint, editor, true),
                metaKey: true,
                shiftKey: true,
              },
            ]
          : []),
        {
          keyCode: VK.RIGHT,
          action: action(moveH, editor, true),
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH, editor, false),
        },
        {
          keyCode: VK.UP,
          action: action(moveV, editor, false),
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV, editor, true),
        },
        {
          keyCode: VK.RIGHT,
          action: action(moveH$1, editor, true),
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH$1, editor, false),
        },
        {
          keyCode: VK.UP,
          action: action(moveV$1, editor, false),
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$1, editor, true),
        },
        {
          keyCode: VK.RIGHT,
          action: action(move$2, editor, caret, true),
        },
        {
          keyCode: VK.LEFT,
          action: action(move$2, editor, caret, false),
        },
        {
          keyCode: VK.RIGHT,
          ctrlKey: !isMac,
          altKey: isMac,
          action: action(moveNextWord, editor, caret),
        },
        {
          keyCode: VK.LEFT,
          ctrlKey: !isMac,
          altKey: isMac,
          action: action(movePrevWord, editor, caret),
        },
        {
          keyCode: VK.UP,
          action: action(moveV$2, editor, false),
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$2, editor, true),
        },
      ],
      evt
    ).each((_) => {
      evt.preventDefault()
    })
  }
  const setup$k = (editor, caret) => {
    editor.on('keydown', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeydownOverride$4(editor, caret, evt)
      }
    })
  }

  const point = (container, offset) => ({
    container,
    offset,
  })

  const DOM$7 = DOMUtils.DOM
  const alwaysNext = (startNode) => (node) => startNode === node ? -1 : 0
  const isBoundary = (dom) => (node) =>
    dom.isBlock(node) ||
    contains$2(['BR', 'IMG', 'HR', 'INPUT'], node.nodeName) ||
    dom.getContentEditable(node) === 'false'
  const textBefore = (node, offset, rootNode) => {
    if (isText$9(node) && offset >= 0) {
      return Optional.some(point(node, offset))
    } else {
      const textSeeker = TextSeeker(DOM$7)
      return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).map((prev) =>
        point(prev.container, prev.container.data.length)
      )
    }
  }
  const textAfter = (node, offset, rootNode) => {
    if (isText$9(node) && offset >= node.length) {
      return Optional.some(point(node, offset))
    } else {
      const textSeeker = TextSeeker(DOM$7)
      return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).map((prev) =>
        point(prev.container, 0)
      )
    }
  }
  const scanLeft = (node, offset, rootNode) => {
    if (!isText$9(node)) {
      return Optional.none()
    }
    const text = node.textContent
    if (offset >= 0 && offset <= text.length) {
      return Optional.some(point(node, offset))
    } else {
      const textSeeker = TextSeeker(DOM$7)
      return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).bind((prev) => {
        const prevText = prev.container.data
        return scanLeft(prev.container, offset + prevText.length, rootNode)
      })
    }
  }
  const scanRight = (node, offset, rootNode) => {
    if (!isText$9(node)) {
      return Optional.none()
    }
    const text = node.textContent
    if (offset <= text.length) {
      return Optional.some(point(node, offset))
    } else {
      const textSeeker = TextSeeker(DOM$7)
      return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).bind((next) =>
        scanRight(next.container, offset - text.length, rootNode)
      )
    }
  }
  const repeatLeft = (dom, node, offset, process, rootNode) => {
    const search = TextSeeker(dom, isBoundary(dom))
    return Optional.from(search.backwards(node, offset, process, rootNode))
  }

  const isValidTextRange = (rng) => rng.collapsed && rng.startContainer.nodeType === 3
  const getText = (rng) =>
    rng
      .toString()
      .replace(/\u00A0/g, ' ')
      .replace(/\uFEFF/g, '')
  const isWhitespace = (chr) => chr !== '' && ' \xA0\f\n\r\t\x0B'.indexOf(chr) !== -1

  const stripTriggerChar = (text, triggerCh) => text.substring(triggerCh.length)
  const findChar = (text, index, ch) => {
    let i
    for (i = index - 1; i >= 0; i--) {
      const char = text.charAt(i)
      if (isWhitespace(char)) {
        return Optional.none()
      }
      if (char === ch) {
        break
      }
    }
    return Optional.some(i)
  }
  const findStart = (dom, initRange, ch, minChars = 0) => {
    if (!isValidTextRange(initRange)) {
      return Optional.none()
    }
    const findTriggerChIndex = (element, offset, text) => findChar(text, offset, ch).getOr(offset)
    const root = dom.getParent(initRange.startContainer, dom.isBlock) || dom.getRoot()
    return repeatLeft(dom, initRange.startContainer, initRange.startOffset, findTriggerChIndex, root).bind((spot) => {
      const range = initRange.cloneRange()
      range.setStart(spot.container, spot.offset)
      range.setEnd(initRange.endContainer, initRange.endOffset)
      if (range.collapsed) {
        return Optional.none()
      }
      const text = getText(range)
      const triggerCharIndex = text.lastIndexOf(ch)
      if (triggerCharIndex !== 0 || stripTriggerChar(text, ch).length < minChars) {
        return Optional.none()
      } else {
        return Optional.some({
          text: stripTriggerChar(text, ch),
          range,
          triggerChar: ch,
        })
      }
    })
  }
  const getContext = (dom, initRange, ch, minChars = 0) =>
    detect$1(SugarElement.fromDom(initRange.startContainer)).fold(
      () => findStart(dom, initRange, ch, minChars),
      (elm) => {
        const range = dom.createRng()
        range.selectNode(elm.dom)
        const text = getText(range)
        return Optional.some({
          range,
          text: stripTriggerChar(text, ch),
          triggerChar: ch,
        })
      }
    )

  const isText$1 = (node) => node.nodeType === TEXT
  const isElement = (node) => node.nodeType === ELEMENT
  const toLast = (node) => {
    if (isText$1(node)) {
      return point(node, node.data.length)
    } else {
      const children = node.childNodes
      return children.length > 0 ? toLast(children[children.length - 1]) : point(node, children.length)
    }
  }
  const toLeaf = (node, offset) => {
    const children = node.childNodes
    if (children.length > 0 && offset < children.length) {
      return toLeaf(children[offset], 0)
    } else if (children.length > 0 && isElement(node) && children.length === offset) {
      return toLast(children[children.length - 1])
    } else {
      return point(node, offset)
    }
  }

  const isPreviousCharContent = (dom, leaf) => {
    const root = dom.getParent(leaf.container, dom.isBlock)
    return repeatLeft(dom, leaf.container, leaf.offset, (_element, offset) => (offset === 0 ? -1 : offset), root)
      .filter((spot) => {
        const char = spot.container.data.charAt(spot.offset - 1)
        return !isWhitespace(char)
      })
      .isSome()
  }
  const isStartOfWord = (dom) => (rng) => {
    const leaf = toLeaf(rng.startContainer, rng.startOffset)
    return !isPreviousCharContent(dom, leaf)
  }
  const getTriggerContext = (dom, initRange, database) =>
    findMap(database.triggerChars, (ch) => getContext(dom, initRange, ch))
  const lookup = (editor, getDatabase) => {
    const database = getDatabase()
    const rng = editor.selection.getRng()
    return getTriggerContext(editor.dom, rng, database).bind((context) =>
      lookupWithContext(editor, getDatabase, context)
    )
  }
  const lookupWithContext = (editor, getDatabase, context, fetchOptions = {}) => {
    const database = getDatabase()
    const rng = editor.selection.getRng()
    const startText = rng.startContainer.nodeValue
    const autocompleters = filter$6(
      database.lookupByChar(context.triggerChar),
      (autocompleter) =>
        context.text.length >= autocompleter.minChars &&
        autocompleter.matches.getOrThunk(() => isStartOfWord(editor.dom))(context.range, startText, context.text)
    )
    if (autocompleters.length === 0) {
      return Optional.none()
    }
    const lookupData = Promise.all(
      map$3(autocompleters, (ac) => {
        const fetchResult = ac.fetch(context.text, ac.maxResults, fetchOptions)
        return fetchResult.then((results) => ({
          matchText: context.text,
          items: results,
          columns: ac.columns,
          onAction: ac.onAction,
          highlightOn: ac.highlightOn,
        }))
      })
    )
    return Optional.some({
      lookupData,
      context,
    })
  }

  var SimpleResultType
  ;(function (SimpleResultType) {
    SimpleResultType[(SimpleResultType['Error'] = 0)] = 'Error'
    SimpleResultType[(SimpleResultType['Value'] = 1)] = 'Value'
  })(SimpleResultType || (SimpleResultType = {}))
  const fold$1 = (res, onError, onValue) =>
    res.stype === SimpleResultType.Error ? onError(res.serror) : onValue(res.svalue)
  const partition = (results) => {
    const values = []
    const errors = []
    each$f(results, (obj) => {
      fold$1(
        obj,
        (err) => errors.push(err),
        (val) => values.push(val)
      )
    })
    return {
      values,
      errors,
    }
  }
  const mapError = (res, f) => {
    if (res.stype === SimpleResultType.Error) {
      return {
        stype: SimpleResultType.Error,
        serror: f(res.serror),
      }
    } else {
      return res
    }
  }
  const map = (res, f) => {
    if (res.stype === SimpleResultType.Value) {
      return {
        stype: SimpleResultType.Value,
        svalue: f(res.svalue),
      }
    } else {
      return res
    }
  }
  const bind = (res, f) => {
    if (res.stype === SimpleResultType.Value) {
      return f(res.svalue)
    } else {
      return res
    }
  }
  const bindError = (res, f) => {
    if (res.stype === SimpleResultType.Error) {
      return f(res.serror)
    } else {
      return res
    }
  }
  const svalue = (v) => ({
    stype: SimpleResultType.Value,
    svalue: v,
  })
  const serror = (e) => ({
    stype: SimpleResultType.Error,
    serror: e,
  })
  const toResult = (res) => fold$1(res, Result.error, Result.value)
  const fromResult = (res) => res.fold(serror, svalue)
  const SimpleResult = {
    fromResult,
    toResult,
    svalue,
    partition,
    serror,
    bind,
    bindError,
    map,
    mapError,
    fold: fold$1,
  }

  const formatObj = (input) => {
    return isObject(input) && keys(input).length > 100 ? ' removed due to size' : JSON.stringify(input, null, 2)
  }
  const formatErrors = (errors) => {
    const es =
      errors.length > 10
        ? errors.slice(0, 10).concat([
            {
              path: [],
              getErrorInfo: constant('... (only showing first ten failures)'),
            },
          ])
        : errors
    return map$3(es, (e) => {
      return 'Failed path: (' + e.path.join(' > ') + ')\n' + e.getErrorInfo()
    })
  }

  const nu = (path, getErrorInfo) => {
    return SimpleResult.serror([
      {
        path,
        getErrorInfo,
      },
    ])
  }
  const missingRequired = (path, key, obj) =>
    nu(path, () => 'Could not find valid *required* value for "' + key + '" in ' + formatObj(obj))
  const missingKey = (path, key) => nu(path, () => 'Choice schema did not contain choice key: "' + key + '"')
  const missingBranch = (path, branches, branch) =>
    nu(path, () => 'The chosen schema: "' + branch + '" did not exist in branches: ' + formatObj(branches))
  const custom = (path, err) => nu(path, constant(err))

  const chooseFrom = (path, input, branches, ch) => {
    const fields = get$a(branches, ch)
    return fields.fold(
      () => missingBranch(path, branches, ch),
      (vp) => vp.extract(path.concat(['branch: ' + ch]), input)
    )
  }
  const choose$1 = (key, branches) => {
    const extract = (path, input) => {
      const choice = get$a(input, key)
      return choice.fold(
        () => missingKey(path, key),
        (chosen) => chooseFrom(path, input, branches, chosen)
      )
    }
    const toString = () => 'chooseOn(' + key + '). Possible values: ' + keys(branches)
    return {
      extract,
      toString,
    }
  }

  const shallow = (old, nu) => {
    return nu
  }
  const deep = (old, nu) => {
    const bothObjects = isPlainObject(old) && isPlainObject(nu)
    return bothObjects ? deepMerge(old, nu) : nu
  }
  const baseMerge = (merger) => {
    return (...objects) => {
      if (objects.length === 0) {
        throw new Error(`Can't merge zero objects`)
      }
      const ret = {}
      for (let j = 0; j < objects.length; j++) {
        const curObject = objects[j]
        for (const key in curObject) {
          if (has$2(curObject, key)) {
            ret[key] = merger(ret[key], curObject[key])
          }
        }
      }
      return ret
    }
  }
  const deepMerge = baseMerge(deep)
  const merge = baseMerge(shallow)

  const required = () => ({
    tag: 'required',
    process: {},
  })
  const defaultedThunk = (fallbackThunk) => ({
    tag: 'defaultedThunk',
    process: fallbackThunk,
  })
  const defaulted$1 = (fallback) => defaultedThunk(constant(fallback))
  const asOption = () => ({
    tag: 'option',
    process: {},
  })

  const mergeValues = (values, base) =>
    values.length > 0 ? SimpleResult.svalue(deepMerge(base, merge.apply(undefined, values))) : SimpleResult.svalue(base)
  const mergeErrors = (errors) => compose(SimpleResult.serror, flatten)(errors)
  const consolidateObj = (objects, base) => {
    const partition = SimpleResult.partition(objects)
    return partition.errors.length > 0 ? mergeErrors(partition.errors) : mergeValues(partition.values, base)
  }
  const consolidateArr = (objects) => {
    const partitions = SimpleResult.partition(objects)
    return partitions.errors.length > 0 ? mergeErrors(partitions.errors) : SimpleResult.svalue(partitions.values)
  }
  const ResultCombine = {
    consolidateObj,
    consolidateArr,
  }

  const field$1 = (key, newKey, presence, prop) => ({
    tag: 'field',
    key,
    newKey,
    presence,
    prop,
  })
  const customField$1 = (newKey, instantiator) => ({
    tag: 'custom',
    newKey,
    instantiator,
  })
  const fold = (value, ifField, ifCustom) => {
    switch (value.tag) {
      case 'field':
        return ifField(value.key, value.newKey, value.presence, value.prop)
      case 'custom':
        return ifCustom(value.newKey, value.instantiator)
    }
  }

  const value = (validator) => {
    const extract = (path, val) => {
      return SimpleResult.bindError(validator(val), (err) => custom(path, err))
    }
    const toString = constant('val')
    return {
      extract,
      toString,
    }
  }
  const anyValue$1 = value(SimpleResult.svalue)

  const requiredAccess = (path, obj, key, bundle) => get$a(obj, key).fold(() => missingRequired(path, key, obj), bundle)
  const fallbackAccess = (obj, key, fallback, bundle) => {
    const v = get$a(obj, key).getOrThunk(() => fallback(obj))
    return bundle(v)
  }
  const optionAccess = (obj, key, bundle) => bundle(get$a(obj, key))
  const optionDefaultedAccess = (obj, key, fallback, bundle) => {
    const opt = get$a(obj, key).map((val) => (val === true ? fallback(obj) : val))
    return bundle(opt)
  }
  const extractField = (field, path, obj, key, prop) => {
    const bundle = (av) => prop.extract(path.concat([key]), av)
    const bundleAsOption = (optValue) =>
      optValue.fold(
        () => SimpleResult.svalue(Optional.none()),
        (ov) => {
          const result = prop.extract(path.concat([key]), ov)
          return SimpleResult.map(result, Optional.some)
        }
      )
    switch (field.tag) {
      case 'required':
        return requiredAccess(path, obj, key, bundle)
      case 'defaultedThunk':
        return fallbackAccess(obj, key, field.process, bundle)
      case 'option':
        return optionAccess(obj, key, bundleAsOption)
      case 'defaultedOptionThunk':
        return optionDefaultedAccess(obj, key, field.process, bundleAsOption)
      case 'mergeWithThunk': {
        return fallbackAccess(obj, key, constant({}), (v) => {
          const result = deepMerge(field.process(obj), v)
          return bundle(result)
        })
      }
    }
  }
  const extractFields = (path, obj, fields) => {
    const success = {}
    const errors = []
    for (const field of fields) {
      fold(
        field,
        (key, newKey, presence, prop) => {
          const result = extractField(presence, path, obj, key, prop)
          SimpleResult.fold(
            result,
            (err) => {
              errors.push(...err)
            },
            (res) => {
              success[newKey] = res
            }
          )
        },
        (newKey, instantiator) => {
          success[newKey] = instantiator(obj)
        }
      )
    }
    return errors.length > 0 ? SimpleResult.serror(errors) : SimpleResult.svalue(success)
  }
  const objOf = (values) => {
    const extract = (path, o) => extractFields(path, o, values)
    const toString = () => {
      const fieldStrings = map$3(values, (value) =>
        fold(
          value,
          (key, _okey, _presence, prop) => key + ' -> ' + prop.toString(),
          (newKey, _instantiator) => 'state(' + newKey + ')'
        )
      )
      return 'obj{\n' + fieldStrings.join('\n') + '}'
    }
    return {
      extract,
      toString,
    }
  }
  const arrOf = (prop) => {
    const extract = (path, array) => {
      const results = map$3(array, (a, i) => prop.extract(path.concat(['[' + i + ']']), a))
      return ResultCombine.consolidateArr(results)
    }
    const toString = () => 'array(' + prop.toString() + ')'
    return {
      extract,
      toString,
    }
  }

  const valueOf = (validator) => value((v) => validator(v).fold(SimpleResult.serror, SimpleResult.svalue))
  const extractValue = (label, prop, obj) => {
    const res = prop.extract([label], obj)
    return SimpleResult.mapError(res, (errs) => ({
      input: obj,
      errors: errs,
    }))
  }
  const asRaw = (label, prop, obj) => SimpleResult.toResult(extractValue(label, prop, obj))
  const formatError = (errInfo) => {
    return 'Errors: \n' + formatErrors(errInfo.errors).join('\n') + '\n\nInput object: ' + formatObj(errInfo.input)
  }
  const choose = (key, branches) => choose$1(key, map$2(branches, objOf))

  const anyValue = constant(anyValue$1)
  const typedValue = (validator, expectedType) =>
    value((a) => {
      const actualType = typeof a
      return validator(a)
        ? SimpleResult.svalue(a)
        : SimpleResult.serror(`Expected type: ${expectedType} but got: ${actualType}`)
    })
  const number = typedValue(isNumber, 'number')
  const string = typedValue(isString, 'string')
  const boolean = typedValue(isBoolean, 'boolean')
  const functionProcessor = typedValue(isFunction, 'function')

  const field = field$1
  const customField = customField$1
  const validateEnum = (values) =>
    valueOf((value) =>
      contains$2(values, value)
        ? Result.value(value)
        : Result.error(`Unsupported value: "${value}", choose one of "${values.join(', ')}".`)
    )
  const requiredOf = (key, schema) => field(key, key, required(), schema)
  const requiredString = (key) => requiredOf(key, string)
  const requiredFunction = (key) => requiredOf(key, functionProcessor)
  const requiredArrayOf = (key, schema) => field(key, key, required(), arrOf(schema))
  const optionOf = (key, schema) => field(key, key, asOption(), schema)
  const optionString = (key) => optionOf(key, string)
  const optionFunction = (key) => optionOf(key, functionProcessor)
  const defaulted = (key, fallback) => field(key, key, defaulted$1(fallback), anyValue())
  const defaultedOf = (key, fallback, schema) => field(key, key, defaulted$1(fallback), schema)
  const defaultedNumber = (key, fallback) => defaultedOf(key, fallback, number)
  const defaultedString = (key, fallback) => defaultedOf(key, fallback, string)
  const defaultedStringEnum = (key, fallback, values) => defaultedOf(key, fallback, validateEnum(values))
  const defaultedBoolean = (key, fallback) => defaultedOf(key, fallback, boolean)
  const defaultedFunction = (key, fallback) => defaultedOf(key, fallback, functionProcessor)
  const defaultedArrayOf = (key, fallback, schema) => defaultedOf(key, fallback, arrOf(schema))

  const type = requiredString('type')
  const fetch$1 = requiredFunction('fetch')
  const onAction = requiredFunction('onAction')
  const onSetup = defaultedFunction('onSetup', () => noop)
  const optionalText = optionString('text')
  const optionalIcon = optionString('icon')
  const optionalTooltip = optionString('tooltip')
  const optionalLabel = optionString('label')
  const active = defaultedBoolean('active', false)
  const enabled = defaultedBoolean('enabled', true)
  const primary = defaultedBoolean('primary', false)
  const defaultedColumns = (num) => defaulted('columns', num)
  const defaultedType = (type) => defaultedString('type', type)

  const autocompleterSchema = objOf([
    type,
    requiredString('ch'),
    defaultedNumber('minChars', 1),
    defaultedColumns(1),
    defaultedNumber('maxResults', 10),
    optionFunction('matches'),
    fetch$1,
    onAction,
    defaultedArrayOf('highlightOn', [], string),
  ])
  const createAutocompleter = (spec) => asRaw('Autocompleter', autocompleterSchema, spec)

  const baseToolbarButtonFields = [enabled, optionalTooltip, optionalIcon, optionalText, onSetup]

  const baseToolbarToggleButtonFields = [active].concat(baseToolbarButtonFields)

  const contextBarFields = [
    defaultedFunction('predicate', never),
    defaultedStringEnum('scope', 'node', ['node', 'editor']),
    defaultedStringEnum('position', 'selection', ['node', 'selection', 'line']),
  ]

  const contextButtonFields = baseToolbarButtonFields.concat([
    defaultedType('contextformbutton'),
    primary,
    onAction,
    customField('original', identity),
  ])
  const contextToggleButtonFields = baseToolbarToggleButtonFields.concat([
    defaultedType('contextformbutton'),
    primary,
    onAction,
    customField('original', identity),
  ])
  const launchButtonFields = baseToolbarButtonFields.concat([defaultedType('contextformbutton')])
  const launchToggleButtonFields = baseToolbarToggleButtonFields.concat([defaultedType('contextformtogglebutton')])
  const toggleOrNormal = choose('type', {
    contextformbutton: contextButtonFields,
    contextformtogglebutton: contextToggleButtonFields,
  })
  objOf(
    [
      defaultedType('contextform'),
      defaultedFunction('initValue', constant('')),
      optionalLabel,
      requiredArrayOf('commands', toggleOrNormal),
      optionOf(
        'launch',
        choose('type', {
          contextformbutton: launchButtonFields,
          contextformtogglebutton: launchToggleButtonFields,
        })
      ),
    ].concat(contextBarFields)
  )

  const register$2 = (editor) => {
    const popups = editor.ui.registry.getAll().popups
    const dataset = map$2(popups, (popup) =>
      createAutocompleter(popup).fold((err) => {
        throw new Error(formatError(err))
      }, identity)
    )
    const triggerChars = stringArray(mapToArray(dataset, (v) => v.ch))
    const datasetValues = values(dataset)
    const lookupByChar = (ch) => filter$6(datasetValues, (dv) => dv.ch === ch)
    return {
      dataset,
      triggerChars,
      lookupByChar,
    }
  }

  const setupEditorInput = (editor, api) => {
    const update = last$1(api.load, 50)
    editor.on('keypress compositionend', (e) => {
      if (e.which === 27) {
        return
      }
      update.throttle()
    })
    editor.on('keydown', (e) => {
      const keyCode = e.which
      if (keyCode === 8) {
        update.throttle()
      } else if (keyCode === 27) {
        api.cancelIfNecessary()
      }
    })
    editor.on('remove', update.cancel)
  }
  const setup$j = (editor) => {
    const activeAutocompleter = value$2()
    const uiActive = Cell(false)
    const isActive = activeAutocompleter.isSet
    const cancelIfNecessary = () => {
      if (isActive()) {
        removeAutocompleterDecoration(editor)
        fireAutocompleterEnd(editor)
        uiActive.set(false)
        activeAutocompleter.clear()
      }
    }
    const commenceIfNecessary = (context) => {
      if (!isActive()) {
        addAutocompleterDecoration(editor, context.range)
        activeAutocompleter.set({
          triggerChar: context.triggerChar,
          matchLength: context.text.length,
        })
      }
    }
    const getAutocompleters = cached(() => register$2(editor))
    const doLookup = (fetchOptions) =>
      activeAutocompleter
        .get()
        .map((ac) =>
          getContext(editor.dom, editor.selection.getRng(), ac.triggerChar).bind((newContext) =>
            lookupWithContext(editor, getAutocompleters, newContext, fetchOptions)
          )
        )
        .getOrThunk(() => lookup(editor, getAutocompleters))
    const load = (fetchOptions) => {
      doLookup(fetchOptions).fold(cancelIfNecessary, (lookupInfo) => {
        commenceIfNecessary(lookupInfo.context)
        lookupInfo.lookupData.then((lookupData) => {
          activeAutocompleter.get().map((ac) => {
            const context = lookupInfo.context
            if (ac.triggerChar === context.triggerChar) {
              if (context.text.length - ac.matchLength >= 10) {
                cancelIfNecessary()
              } else {
                activeAutocompleter.set({
                  ...ac,
                  matchLength: context.text.length,
                })
                if (uiActive.get()) {
                  fireAutocompleterUpdate(editor, { lookupData })
                } else {
                  uiActive.set(true)
                  fireAutocompleterStart(editor, { lookupData })
                }
              }
            }
          })
        })
      })
    }
    editor.addCommand('mceAutocompleterReload', (_ui, value) => {
      const fetchOptions = isObject(value) ? value.fetchOptions : {}
      load(fetchOptions)
    })
    editor.addCommand('mceAutocompleterClose', cancelIfNecessary)
    setupEditorInput(editor, {
      cancelIfNecessary,
      load,
    })
  }

  const createAndFireInputEvent =
    (eventType) =>
    (editor, inputType, specifics = {}) => {
      const target = editor.getBody()
      const overrides = {
        bubbles: true,
        composed: true,
        data: null,
        isComposing: false,
        detail: 0,
        view: null,
        target,
        currentTarget: target,
        eventPhase: Event.AT_TARGET,
        originalTarget: target,
        explicitOriginalTarget: target,
        isTrusted: false,
        srcElement: target,
        cancelable: false,
        preventDefault: noop,
        inputType,
      }
      const input = clone$3(new InputEvent(eventType))
      return editor.dispatch(eventType, {
        ...input,
        ...overrides,
        ...specifics,
      })
    }
  const fireFakeInputEvent = createAndFireInputEvent('input')
  const fireFakeBeforeInputEvent = createAndFireInputEvent('beforeinput')

  const executeKeydownOverride$3 = (editor, caret, evt) => {
    const inputType = evt.keyCode === VK.BACKSPACE ? 'deleteContentBackward' : 'deleteContentForward'
    executeWithDelayedAction(
      [
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete, editor),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$5, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$5, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$6, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$6, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$3, editor, caret, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$3, editor, caret, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$9, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$9, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$4, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$4, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$1, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$1, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$7, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$7, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$8, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$8, editor, true),
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$2, editor, false),
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$2, editor, true),
        },
      ],
      evt
    ).each((applyAction) => {
      evt.preventDefault()
      const beforeInput = fireFakeBeforeInputEvent(editor, inputType)
      if (!beforeInput.isDefaultPrevented()) {
        applyAction()
        fireFakeInputEvent(editor, inputType)
      }
    })
  }
  const executeKeyupOverride = (editor, evt) => {
    execute(
      [
        {
          keyCode: VK.BACKSPACE,
          action: action(paddEmptyElement, editor),
        },
        {
          keyCode: VK.DELETE,
          action: action(paddEmptyElement, editor),
        },
      ],
      evt
    )
  }
  const setup$i = (editor, caret) => {
    editor.on('keydown', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeydownOverride$3(editor, caret, evt)
      }
    })
    editor.on('keyup', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeyupOverride(editor, evt)
      }
    })
  }

  const firstNonWhiteSpaceNodeSibling = (node) => {
    while (node) {
      if (node.nodeType === 1 || (node.nodeType === 3 && node.data && /[\r\n\s]/.test(node.data))) {
        return node
      }
      node = node.nextSibling
    }
  }
  const moveToCaretPosition = (editor, root) => {
    let node,
      lastNode = root
    const dom = editor.dom
    const moveCaretBeforeOnEnterElementsMap = editor.schema.getMoveCaretBeforeOnEnterElements()
    if (!root) {
      return
    }
    if (/^(LI|DT|DD)$/.test(root.nodeName)) {
      const firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild)
      if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
        root.insertBefore(dom.doc.createTextNode(nbsp), root.firstChild)
      }
    }
    const rng = dom.createRng()
    root.normalize()
    if (root.hasChildNodes()) {
      const walker = new DomTreeWalker(root, root)
      while ((node = walker.current())) {
        if (isText$9(node)) {
          rng.setStart(node, 0)
          rng.setEnd(node, 0)
          break
        }
        if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
          rng.setStartBefore(node)
          rng.setEndBefore(node)
          break
        }
        lastNode = node
        node = walker.next()
      }
      if (!node) {
        rng.setStart(lastNode, 0)
        rng.setEnd(lastNode, 0)
      }
    } else {
      if (isBr$6(root)) {
        if (root.nextSibling && dom.isBlock(root.nextSibling)) {
          rng.setStartBefore(root)
          rng.setEndBefore(root)
        } else {
          rng.setStartAfter(root)
          rng.setEndAfter(root)
        }
      } else {
        rng.setStart(root, 0)
        rng.setEnd(root, 0)
      }
    }
    editor.selection.setRng(rng)
    scrollRangeIntoView(editor, rng)
  }
  const getEditableRoot$1 = (dom, node) => {
    const root = dom.getRoot()
    let parent, editableRoot
    parent = node
    while (parent !== root && dom.getContentEditable(parent) !== 'false') {
      if (dom.getContentEditable(parent) === 'true') {
        editableRoot = parent
      }
      parent = parent.parentNode
    }
    return parent !== root ? editableRoot : root
  }
  const getParentBlock$1 = (editor) => {
    return Optional.from(editor.dom.getParent(editor.selection.getStart(true), editor.dom.isBlock))
  }
  const getParentBlockName = (editor) => {
    return getParentBlock$1(editor).fold(constant(''), (parentBlock) => {
      return parentBlock.nodeName.toUpperCase()
    })
  }
  const isListItemParentBlock = (editor) => {
    return getParentBlock$1(editor)
      .filter((elm) => {
        return isListItem(SugarElement.fromDom(elm))
      })
      .isSome()
  }

  const hasFirstChild = (elm, name) => {
    return elm.firstChild && elm.firstChild.nodeName === name
  }
  const isFirstChild = (elm) => {
    var _a
    return ((_a = elm.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild) === elm
  }
  const hasParent = (elm, parentName) => {
    return elm && elm.parentNode && elm.parentNode.nodeName === parentName
  }
  const isListBlock = (elm) => {
    return elm && /^(OL|UL|LI)$/.test(elm.nodeName)
  }
  const isNestedList = (elm) => {
    return isListBlock(elm) && isListBlock(elm.parentNode)
  }
  const getContainerBlock = (containerBlock) => {
    const containerBlockParent = containerBlock.parentNode
    if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
      return containerBlockParent
    }
    return containerBlock
  }
  const isFirstOrLastLi = (containerBlock, parentBlock, first) => {
    let node = containerBlock[first ? 'firstChild' : 'lastChild']
    while (node) {
      if (isElement$6(node)) {
        break
      }
      node = node[first ? 'nextSibling' : 'previousSibling']
    }
    return node === parentBlock
  }
  const insert$3 = (editor, createNewBlock, containerBlock, parentBlock, newBlockName) => {
    const dom = editor.dom
    const rng = editor.selection.getRng()
    if (containerBlock === editor.getBody()) {
      return
    }
    if (isNestedList(containerBlock)) {
      newBlockName = 'LI'
    }
    let newBlock = createNewBlock(newBlockName)
    if (isFirstOrLastLi(containerBlock, parentBlock, true) && isFirstOrLastLi(containerBlock, parentBlock, false)) {
      if (hasParent(containerBlock, 'LI')) {
        const containerBlockParent = getContainerBlock(containerBlock)
        dom.insertAfter(newBlock, containerBlockParent)
        if (isFirstChild(containerBlock)) {
          dom.remove(containerBlockParent)
        } else {
          dom.remove(containerBlock)
        }
      } else {
        dom.replace(newBlock, containerBlock)
      }
    } else if (isFirstOrLastLi(containerBlock, parentBlock, true)) {
      if (hasParent(containerBlock, 'LI')) {
        dom.insertAfter(newBlock, getContainerBlock(containerBlock))
        newBlock.appendChild(dom.doc.createTextNode(' '))
        newBlock.appendChild(containerBlock)
      } else {
        containerBlock.parentNode.insertBefore(newBlock, containerBlock)
      }
      dom.remove(parentBlock)
    } else if (isFirstOrLastLi(containerBlock, parentBlock, false)) {
      dom.insertAfter(newBlock, getContainerBlock(containerBlock))
      dom.remove(parentBlock)
    } else {
      containerBlock = getContainerBlock(containerBlock)
      const tmpRng = rng.cloneRange()
      tmpRng.setStartAfter(parentBlock)
      tmpRng.setEndAfter(containerBlock)
      const fragment = tmpRng.extractContents()
      if (newBlockName === 'LI' && hasFirstChild(fragment, 'LI')) {
        newBlock = fragment.firstChild
        dom.insertAfter(fragment, containerBlock)
      } else {
        dom.insertAfter(fragment, containerBlock)
        dom.insertAfter(newBlock, containerBlock)
      }
      dom.remove(parentBlock)
    }
    moveToCaretPosition(editor, newBlock)
  }

  const trimZwsp = (fragment) => {
    each$f(descendants$1(SugarElement.fromDom(fragment), isText$a), (text) => {
      const rawNode = text.dom
      rawNode.nodeValue = trim$1(rawNode.nodeValue)
    })
  }
  const isEmptyAnchor = (dom, elm) => {
    return elm && elm.nodeName === 'A' && dom.isEmpty(elm)
  }
  const isTableCell = (node) => {
    return node && /^(TD|TH|CAPTION)$/.test(node.nodeName)
  }
  const emptyBlock = (elm) => {
    elm.innerHTML = '<br data-mce-bogus="1">'
  }
  const containerAndSiblingName = (container, nodeName) => {
    return (
      container.nodeName === nodeName || (container.previousSibling && container.previousSibling.nodeName === nodeName)
    )
  }
  const canSplitBlock = (dom, node) => {
    return (
      node &&
      dom.isBlock(node) &&
      !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
      !/^(fixed|absolute)/i.test(node.style.position) &&
      dom.getContentEditable(node) !== 'true'
    )
  }
  const trimInlineElementsOnLeftSideOfBlock = (dom, nonEmptyElementsMap, block) => {
    let node = block
    const firstChilds = []
    let i
    if (!node) {
      return
    }
    while ((node = node.firstChild)) {
      if (dom.isBlock(node)) {
        return
      }
      if (isElement$6(node) && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
        firstChilds.push(node)
      }
    }
    i = firstChilds.length
    while (i--) {
      node = firstChilds[i]
      if (!node.hasChildNodes() || (node.firstChild === node.lastChild && node.firstChild.nodeValue === '')) {
        dom.remove(node)
      } else {
        if (isEmptyAnchor(dom, node)) {
          dom.remove(node)
        }
      }
    }
  }
  const normalizeZwspOffset = (start, container, offset) => {
    if (isText$9(container) === false) {
      return offset
    } else if (start) {
      return offset === 1 && container.data.charAt(offset - 1) === ZWSP$1 ? 0 : offset
    } else {
      return offset === container.data.length - 1 && container.data.charAt(offset) === ZWSP$1
        ? container.data.length
        : offset
    }
  }
  const includeZwspInRange = (rng) => {
    const newRng = rng.cloneRange()
    newRng.setStart(rng.startContainer, normalizeZwspOffset(true, rng.startContainer, rng.startOffset))
    newRng.setEnd(rng.endContainer, normalizeZwspOffset(false, rng.endContainer, rng.endOffset))
    return newRng
  }
  const trimLeadingLineBreaks = (node) => {
    do {
      if (isText$9(node)) {
        node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '')
      }
      node = node.firstChild
    } while (node)
  }
  const getEditableRoot = (dom, node) => {
    const root = dom.getRoot()
    let parent, editableRoot
    parent = node
    while (parent !== root && dom.getContentEditable(parent) !== 'false') {
      if (dom.getContentEditable(parent) === 'true') {
        editableRoot = parent
      }
      parent = parent.parentNode
    }
    return parent !== root ? editableRoot : root
  }
  const applyAttributes = (editor, node, forcedRootBlockAttrs) => {
    const dom = editor.dom
    Optional.from(forcedRootBlockAttrs.style)
      .map(dom.parseStyle)
      .each((attrStyles) => {
        const currentStyles = getAllRaw(SugarElement.fromDom(node))
        const newStyles = {
          ...currentStyles,
          ...attrStyles,
        }
        dom.setStyles(node, newStyles)
      })
    const attrClassesOpt = Optional.from(forcedRootBlockAttrs.class).map((attrClasses) => attrClasses.split(/\s+/))
    const currentClassesOpt = Optional.from(node.className).map((currentClasses) =>
      filter$6(currentClasses.split(/\s+/), (clazz) => clazz !== '')
    )
    lift2(attrClassesOpt, currentClassesOpt, (attrClasses, currentClasses) => {
      const filteredClasses = filter$6(currentClasses, (clazz) => !contains$2(attrClasses, clazz))
      const newClasses = [...attrClasses, ...filteredClasses]
      dom.setAttrib(node, 'class', newClasses.join(' '))
    })
    const appliedAttrs = ['style', 'class']
    const remainingAttrs = filter$5(forcedRootBlockAttrs, (_, attrs) => !contains$2(appliedAttrs, attrs))
    dom.setAttribs(node, remainingAttrs)
  }
  const setForcedBlockAttrs = (editor, node) => {
    const forcedRootBlockName = getForcedRootBlock(editor)
    if (forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
      const forcedRootBlockAttrs = getForcedRootBlockAttrs(editor)
      applyAttributes(editor, node, forcedRootBlockAttrs)
    }
  }
  const wrapSelfAndSiblingsInDefaultBlock = (editor, newBlockName, rng, container, offset) => {
    let newBlock, parentBlock, startNode, node, next, rootBlockName
    const dom = editor.dom,
      editableRoot = getEditableRoot(dom, container)
    parentBlock = dom.getParent(container, dom.isBlock)
    if (!parentBlock || !canSplitBlock(dom, parentBlock)) {
      parentBlock = parentBlock || editableRoot
      if (parentBlock === editor.getBody() || isTableCell(parentBlock)) {
        rootBlockName = parentBlock.nodeName.toLowerCase()
      } else {
        rootBlockName = parentBlock.parentNode.nodeName.toLowerCase()
      }
      if (!parentBlock.hasChildNodes()) {
        newBlock = dom.create(newBlockName)
        setForcedBlockAttrs(editor, newBlock)
        parentBlock.appendChild(newBlock)
        rng.setStart(newBlock, 0)
        rng.setEnd(newBlock, 0)
        return newBlock
      }
      node = container
      while (node.parentNode !== parentBlock) {
        node = node.parentNode
      }
      while (node && !dom.isBlock(node)) {
        startNode = node
        node = node.previousSibling
      }
      if (startNode && editor.schema.isValidChild(rootBlockName, newBlockName.toLowerCase())) {
        newBlock = dom.create(newBlockName)
        setForcedBlockAttrs(editor, newBlock)
        startNode.parentNode.insertBefore(newBlock, startNode)
        node = startNode
        while (node && !dom.isBlock(node)) {
          next = node.nextSibling
          newBlock.appendChild(node)
          node = next
        }
        rng.setStart(container, offset)
        rng.setEnd(container, offset)
      }
    }
    return container
  }
  const addBrToBlockIfNeeded = (dom, block) => {
    block.normalize()
    const lastChild = block.lastChild
    if (!lastChild || /^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true))) {
      dom.add(block, 'br')
    }
  }
  const shouldEndContainer = (editor, container) => {
    const optionValue = shouldEndContainerOnEmptyBlock(editor)
    if (isNullable(container)) {
      return false
    } else if (isString(optionValue)) {
      return contains$2(Tools.explode(optionValue), container.nodeName.toLowerCase())
    } else {
      return optionValue
    }
  }
  const insert$2 = (editor, evt) => {
    let tmpRng, container, offset, parentBlock
    let newBlock, fragment, containerBlock, parentBlockName, isAfterLastNodeInContainer
    const dom = editor.dom
    const schema = editor.schema,
      nonEmptyElementsMap = schema.getNonEmptyElements()
    const rng = editor.selection.getRng()
    const newBlockName = getForcedRootBlock(editor)
    const createNewBlock = (name) => {
      let node = container,
        block,
        clonedNode,
        caretNode
      const textInlineElements = schema.getTextInlineElements()
      if (name || parentBlockName === 'TABLE' || parentBlockName === 'HR') {
        block = dom.create(name || newBlockName)
      } else {
        block = parentBlock.cloneNode(false)
      }
      caretNode = block
      if (shouldKeepStyles(editor) === false) {
        dom.setAttrib(block, 'style', null)
        dom.setAttrib(block, 'class', null)
      } else {
        do {
          if (textInlineElements[node.nodeName]) {
            if (isCaretNode(node) || isBookmarkNode$1(node)) {
              continue
            }
            clonedNode = node.cloneNode(false)
            dom.setAttrib(clonedNode, 'id', '')
            if (block.hasChildNodes()) {
              clonedNode.appendChild(block.firstChild)
              block.appendChild(clonedNode)
            } else {
              caretNode = clonedNode
              block.appendChild(clonedNode)
            }
          }
        } while ((node = node.parentNode) && node !== editableRoot)
      }
      setForcedBlockAttrs(editor, block)
      emptyBlock(caretNode)
      return block
    }
    const isCaretAtStartOrEndOfBlock = (start) => {
      let node, name
      const normalizedOffset = normalizeZwspOffset(start, container, offset)
      if (isText$9(container) && (start ? normalizedOffset > 0 : normalizedOffset < container.nodeValue.length)) {
        return false
      }
      if (container.parentNode === parentBlock && isAfterLastNodeInContainer && !start) {
        return true
      }
      if (start && isElement$6(container) && container === parentBlock.firstChild) {
        return true
      }
      if (containerAndSiblingName(container, 'TABLE') || containerAndSiblingName(container, 'HR')) {
        return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start)
      }
      const walker = new DomTreeWalker(container, parentBlock)
      if (isText$9(container)) {
        if (start && normalizedOffset === 0) {
          walker.prev()
        } else if (!start && normalizedOffset === container.nodeValue.length) {
          walker.next()
        }
      }
      while ((node = walker.current())) {
        if (isElement$6(node)) {
          if (!node.getAttribute('data-mce-bogus')) {
            name = node.nodeName.toLowerCase()
            if (nonEmptyElementsMap[name] && name !== 'br') {
              return false
            }
          }
        } else if (isText$9(node) && !isWhitespaceText(node.nodeValue)) {
          return false
        }
        if (start) {
          walker.prev()
        } else {
          walker.next()
        }
      }
      return true
    }
    const insertNewBlockAfter = () => {
      if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName !== 'HGROUP') {
        newBlock = createNewBlock(newBlockName)
      } else {
        newBlock = createNewBlock()
      }
      if (
        shouldEndContainer(editor, containerBlock) &&
        canSplitBlock(dom, containerBlock) &&
        dom.isEmpty(parentBlock)
      ) {
        newBlock = dom.split(containerBlock, parentBlock)
      } else {
        dom.insertAfter(newBlock, parentBlock)
      }
      moveToCaretPosition(editor, newBlock)
    }
    normalize$2(dom, rng).each((normRng) => {
      rng.setStart(normRng.startContainer, normRng.startOffset)
      rng.setEnd(normRng.endContainer, normRng.endOffset)
    })
    container = rng.startContainer
    offset = rng.startOffset
    const shiftKey = !!(evt && evt.shiftKey)
    const ctrlKey = !!(evt && evt.ctrlKey)
    if (isElement$6(container) && container.hasChildNodes()) {
      isAfterLastNodeInContainer = offset > container.childNodes.length - 1
      container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container
      if (isAfterLastNodeInContainer && isText$9(container)) {
        offset = container.nodeValue.length
      } else {
        offset = 0
      }
    }
    const editableRoot = getEditableRoot(dom, container)
    if (!editableRoot) {
      return
    }
    if (!shiftKey) {
      container = wrapSelfAndSiblingsInDefaultBlock(editor, newBlockName, rng, container, offset)
    }
    parentBlock = dom.getParent(container, dom.isBlock)
    containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null
    parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''
    const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''
    if (containerBlockName === 'LI' && !ctrlKey) {
      parentBlock = containerBlock
      containerBlock = containerBlock.parentNode
      parentBlockName = containerBlockName
    }
    if (/^(LI|DT|DD)$/.test(parentBlockName)) {
      if (dom.isEmpty(parentBlock)) {
        insert$3(editor, createNewBlock, containerBlock, parentBlock, newBlockName)
        return
      }
    }
    if (parentBlock === editor.getBody()) {
      return
    }
    if (isCaretContainerBlock$1(parentBlock)) {
      newBlock = showCaretContainerBlock(parentBlock)
      if (dom.isEmpty(parentBlock)) {
        emptyBlock(parentBlock)
      }
      setForcedBlockAttrs(editor, newBlock)
      moveToCaretPosition(editor, newBlock)
    } else if (isCaretAtStartOrEndOfBlock()) {
      insertNewBlockAfter()
    } else if (isCaretAtStartOrEndOfBlock(true)) {
      newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock)
      moveToCaretPosition(editor, containerAndSiblingName(parentBlock, 'HR') ? newBlock : parentBlock)
    } else {
      tmpRng = includeZwspInRange(rng).cloneRange()
      tmpRng.setEndAfter(parentBlock)
      fragment = tmpRng.extractContents()
      trimZwsp(fragment)
      trimLeadingLineBreaks(fragment)
      newBlock = fragment.firstChild
      dom.insertAfter(fragment, parentBlock)
      trimInlineElementsOnLeftSideOfBlock(dom, nonEmptyElementsMap, newBlock)
      addBrToBlockIfNeeded(dom, parentBlock)
      if (dom.isEmpty(parentBlock)) {
        emptyBlock(parentBlock)
      }
      newBlock.normalize()
      if (dom.isEmpty(newBlock)) {
        dom.remove(newBlock)
        insertNewBlockAfter()
      } else {
        setForcedBlockAttrs(editor, newBlock)
        moveToCaretPosition(editor, newBlock)
      }
    }
    dom.setAttrib(newBlock, 'id', '')
    editor.dispatch('NewBlock', { newBlock })
  }
  const fakeEventName$1 = 'insertParagraph'
  const blockbreak = {
    insert: insert$2,
    fakeEventName: fakeEventName$1,
  }

  const hasRightSideContent = (schema, container, parentBlock) => {
    const walker = new DomTreeWalker(container, parentBlock)
    let node
    const nonEmptyElementsMap = schema.getNonEmptyElements()
    while ((node = walker.next())) {
      if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
        return true
      }
    }
  }
  const moveSelectionToBr = (editor, brElm, extraBr) => {
    const rng = editor.dom.createRng()
    if (!extraBr) {
      rng.setStartAfter(brElm)
      rng.setEndAfter(brElm)
    } else {
      rng.setStartBefore(brElm)
      rng.setEndBefore(brElm)
    }
    editor.selection.setRng(rng)
    scrollRangeIntoView(editor, rng)
  }
  const insertBrAtCaret = (editor, evt) => {
    const selection = editor.selection
    const dom = editor.dom
    const rng = selection.getRng()
    let brElm
    let extraBr
    normalize$2(dom, rng).each((normRng) => {
      rng.setStart(normRng.startContainer, normRng.startOffset)
      rng.setEnd(normRng.endContainer, normRng.endOffset)
    })
    let offset = rng.startOffset
    let container = rng.startContainer
    if (container.nodeType === 1 && container.hasChildNodes()) {
      const isAfterLastNodeInContainer = offset > container.childNodes.length - 1
      container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container
      if (isAfterLastNodeInContainer && container.nodeType === 3) {
        offset = container.nodeValue.length
      } else {
        offset = 0
      }
    }
    let parentBlock = dom.getParent(container, dom.isBlock)
    const containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null
    const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''
    const isControlKey = !!(evt && evt.ctrlKey)
    if (containerBlockName === 'LI' && !isControlKey) {
      parentBlock = containerBlock
    }
    if (container && container.nodeType === 3 && offset >= container.nodeValue.length) {
      if (!hasRightSideContent(editor.schema, container, parentBlock)) {
        brElm = dom.create('br')
        rng.insertNode(brElm)
        rng.setStartAfter(brElm)
        rng.setEndAfter(brElm)
        extraBr = true
      }
    }
    brElm = dom.create('br')
    rangeInsertNode(dom, rng, brElm)
    moveSelectionToBr(editor, brElm, extraBr)
    editor.undoManager.add()
  }
  const insertBrBefore = (editor, inline) => {
    const br = SugarElement.fromTag('br')
    before$3(SugarElement.fromDom(inline), br)
    editor.undoManager.add()
  }
  const insertBrAfter = (editor, inline) => {
    if (!hasBrAfter(editor.getBody(), inline)) {
      after$4(SugarElement.fromDom(inline), SugarElement.fromTag('br'))
    }
    const br = SugarElement.fromTag('br')
    after$4(SugarElement.fromDom(inline), br)
    moveSelectionToBr(editor, br.dom, false)
    editor.undoManager.add()
  }
  const isBeforeBr = (pos) => {
    return isBr$6(pos.getNode())
  }
  const hasBrAfter = (rootNode, startNode) => {
    if (isBeforeBr(CaretPosition.after(startNode))) {
      return true
    } else {
      return nextPosition(rootNode, CaretPosition.after(startNode))
        .map((pos) => {
          return isBr$6(pos.getNode())
        })
        .getOr(false)
    }
  }
  const isAnchorLink = (elm) => {
    return elm && elm.nodeName === 'A' && 'href' in elm
  }
  const isInsideAnchor = (location) => {
    return location.fold(never, isAnchorLink, isAnchorLink, never)
  }
  const readInlineAnchorLocation = (editor) => {
    const isInlineTarget$1 = curry(isInlineTarget, editor)
    const position = CaretPosition.fromRangeStart(editor.selection.getRng())
    return readLocation(isInlineTarget$1, editor.getBody(), position).filter(isInsideAnchor)
  }
  const insertBrOutsideAnchor = (editor, location) => {
    location.fold(noop, curry(insertBrBefore, editor), curry(insertBrAfter, editor), noop)
  }
  const insert$1 = (editor, evt) => {
    const anchorLocation = readInlineAnchorLocation(editor)
    if (anchorLocation.isSome()) {
      anchorLocation.each(curry(insertBrOutsideAnchor, editor))
    } else {
      insertBrAtCaret(editor, evt)
    }
  }
  const fakeEventName = 'insertLineBreak'
  const linebreak = {
    insert: insert$1,
    fakeEventName,
  }

  const matchesSelector = (editor, selector) => {
    return getParentBlock$1(editor)
      .filter((parentBlock) => {
        return selector.length > 0 && is$1(SugarElement.fromDom(parentBlock), selector)
      })
      .isSome()
  }
  const shouldInsertBr = (editor) => {
    return matchesSelector(editor, getBrNewLineSelector(editor))
  }
  const shouldBlockNewLine$1 = (editor) => {
    return matchesSelector(editor, getNoNewLineSelector(editor))
  }

  const newLineAction = Adt.generate([{ br: [] }, { block: [] }, { none: [] }])
  const shouldBlockNewLine = (editor, _shiftKey) => {
    return shouldBlockNewLine$1(editor)
  }
  const inListBlock = (requiredState) => {
    return (editor, _shiftKey) => {
      return isListItemParentBlock(editor) === requiredState
    }
  }
  const inBlock = (blockName, requiredState) => (editor, _shiftKey) => {
    const state = getParentBlockName(editor) === blockName.toUpperCase()
    return state === requiredState
  }
  const inPreBlock = (requiredState) => inBlock('pre', requiredState)
  const inSummaryBlock = () => inBlock('summary', true)
  const shouldPutBrInPre = (requiredState) => {
    return (editor, _shiftKey) => {
      return shouldPutBrInPre$1(editor) === requiredState
    }
  }
  const inBrContext = (editor, _shiftKey) => {
    return shouldInsertBr(editor)
  }
  const hasShiftKey = (_editor, shiftKey) => {
    return shiftKey
  }
  const canInsertIntoEditableRoot = (editor) => {
    const forcedRootBlock = getForcedRootBlock(editor)
    const rootEditable = getEditableRoot$1(editor.dom, editor.selection.getStart())
    return rootEditable && editor.schema.isValidChild(rootEditable.nodeName, forcedRootBlock)
  }
  const match = (predicates, action) => {
    return (editor, shiftKey) => {
      const isMatch = foldl(
        predicates,
        (res, p) => {
          return res && p(editor, shiftKey)
        },
        true
      )
      return isMatch ? Optional.some(action) : Optional.none()
    }
  }
  const getAction = (editor, evt) => {
    return evaluateUntil(
      [
        match([shouldBlockNewLine], newLineAction.none()),
        match([inSummaryBlock()], newLineAction.br()),
        match([inPreBlock(true), shouldPutBrInPre(false), hasShiftKey], newLineAction.br()),
        match([inPreBlock(true), shouldPutBrInPre(false)], newLineAction.block()),
        match([inPreBlock(true), shouldPutBrInPre(true), hasShiftKey], newLineAction.block()),
        match([inPreBlock(true), shouldPutBrInPre(true)], newLineAction.br()),
        match([inListBlock(true), hasShiftKey], newLineAction.br()),
        match([inListBlock(true)], newLineAction.block()),
        match([inBrContext], newLineAction.br()),
        match([hasShiftKey], newLineAction.br()),
        match([canInsertIntoEditableRoot], newLineAction.block()),
      ],
      [editor, !!(evt && evt.shiftKey)]
    ).getOr(newLineAction.none())
  }

  const insertBreak = (breakType, editor, evt) => {
    if (!editor.selection.isCollapsed()) {
      execDeleteCommand(editor)
    }
    if (isNonNullable(evt)) {
      const event = fireFakeBeforeInputEvent(editor, breakType.fakeEventName)
      if (event.isDefaultPrevented()) {
        return
      }
    }
    breakType.insert(editor, evt)
    if (isNonNullable(evt)) {
      fireFakeInputEvent(editor, breakType.fakeEventName)
    }
  }
  const insert = (editor, evt) => {
    const br = () => insertBreak(linebreak, editor, evt)
    const block = () => insertBreak(blockbreak, editor, evt)
    const logicalAction = getAction(editor, evt)
    switch (getNewlineBehavior(editor)) {
      case 'linebreak':
        logicalAction.fold(br, br, noop)
        break
      case 'block':
        logicalAction.fold(block, block, noop)
        break
      case 'invert':
        logicalAction.fold(block, br, noop)
        break
      default:
        logicalAction.fold(br, block, noop)
        break
    }
  }

  const handleEnterKeyEvent = (editor, event) => {
    if (event.isDefaultPrevented()) {
      return
    }
    event.preventDefault()
    endTypingLevelIgnoreLocks(editor.undoManager)
    editor.undoManager.transact(() => {
      insert(editor, event)
    })
  }
  const setup$h = (editor) => {
    editor.on('keydown', (event) => {
      if (event.keyCode === VK.ENTER) {
        handleEnterKeyEvent(editor, event)
      }
    })
  }

  const executeKeydownOverride$2 = (editor, caret, evt) => {
    const isMac = Env.os.isMacOS() || Env.os.isiOS()
    execute(
      [
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint$1, editor, true),
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint$1, editor, false),
        },
        ...(!isMac
          ? [
              {
                keyCode: VK.HOME,
                action: action(selectToEndPoint, editor, false),
                ctrlKey: true,
                shiftKey: true,
              },
              {
                keyCode: VK.END,
                action: action(selectToEndPoint, editor, true),
                ctrlKey: true,
                shiftKey: true,
              },
            ]
          : []),
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint, editor, true),
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint, editor, false),
        },
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint$2, editor, true, caret),
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint$2, editor, false, caret),
        },
      ],
      evt
    ).each((_) => {
      evt.preventDefault()
    })
  }
  const setup$g = (editor, caret) => {
    editor.on('keydown', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeydownOverride$2(editor, caret, evt)
      }
    })
  }

  const setup$f = (editor) => {
    editor.on('input', (e) => {
      if (e.isComposing === false) {
        normalizeNbspsInEditor(editor)
      }
    })
  }

  const platform = detect$2()
  const executeKeyupAction = (editor, caret, evt) => {
    execute(
      [
        {
          keyCode: VK.PAGE_UP,
          action: action(moveToLineEndPoint$2, editor, false, caret),
        },
        {
          keyCode: VK.PAGE_DOWN,
          action: action(moveToLineEndPoint$2, editor, true, caret),
        },
      ],
      evt
    )
  }
  const stopImmediatePropagation = (e) => e.stopImmediatePropagation()
  const isPageUpDown = (evt) => evt.keyCode === VK.PAGE_UP || evt.keyCode === VK.PAGE_DOWN
  const setNodeChangeBlocker = (blocked, editor, block) => {
    if (block && !blocked.get()) {
      editor.on('NodeChange', stopImmediatePropagation, true)
    } else if (!block && blocked.get()) {
      editor.off('NodeChange', stopImmediatePropagation)
    }
    blocked.set(block)
  }
  const setup$e = (editor, caret) => {
    if (platform.os.isMacOS()) {
      return
    }
    const blocked = Cell(false)
    editor.on('keydown', (evt) => {
      if (isPageUpDown(evt)) {
        setNodeChangeBlocker(blocked, editor, true)
      }
    })
    editor.on('keyup', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeyupAction(editor, caret, evt)
      }
      if (isPageUpDown(evt) && blocked.get()) {
        setNodeChangeBlocker(blocked, editor, false)
        editor.nodeChanged()
      }
    })
  }

  const insertTextAtPosition = (text, pos) => {
    const container = pos.container()
    const offset = pos.offset()
    if (isText$9(container)) {
      container.insertData(offset, text)
      return Optional.some(CaretPosition(container, offset + text.length))
    } else {
      return getElementFromPosition(pos).map((elm) => {
        const textNode = SugarElement.fromText(text)
        if (pos.isAtEnd()) {
          after$4(elm, textNode)
        } else {
          before$3(elm, textNode)
        }
        return CaretPosition(textNode.dom, text.length)
      })
    }
  }
  const insertNbspAtPosition = curry(insertTextAtPosition, nbsp)
  const insertSpaceAtPosition = curry(insertTextAtPosition, ' ')

  const locationToCaretPosition = (root) => (location) =>
    location.fold(
      (element) => prevPosition(root.dom, CaretPosition.before(element)),
      (element) => firstPositionIn(element),
      (element) => lastPositionIn(element),
      (element) => nextPosition(root.dom, CaretPosition.after(element))
    )
  const insertInlineBoundarySpaceOrNbsp = (root, pos) => (checkPos) =>
    needsToHaveNbsp(root, checkPos) ? insertNbspAtPosition(pos) : insertSpaceAtPosition(pos)
  const setSelection = (editor) => (pos) => {
    editor.selection.setRng(pos.toRange())
    editor.nodeChanged()
    return true
  }
  const insertSpaceOrNbspAtSelection = (editor) => {
    const pos = CaretPosition.fromRangeStart(editor.selection.getRng())
    const root = SugarElement.fromDom(editor.getBody())
    if (editor.selection.isCollapsed()) {
      const isInlineTarget$1 = curry(isInlineTarget, editor)
      const caretPosition = CaretPosition.fromRangeStart(editor.selection.getRng())
      return readLocation(isInlineTarget$1, editor.getBody(), caretPosition)
        .bind(locationToCaretPosition(root))
        .map((checkPos) => () => insertInlineBoundarySpaceOrNbsp(root, pos)(checkPos).each(setSelection(editor)))
    } else {
      return Optional.none()
    }
  }

  const executeKeydownOverride$1 = (editor, evt) => {
    executeWithDelayedAction(
      [
        {
          keyCode: VK.SPACEBAR,
          action: action(insertSpaceOrNbspAtSelection, editor),
        },
      ],
      evt
    ).each((applyAction) => {
      evt.preventDefault()
      const event = fireFakeBeforeInputEvent(editor, 'insertText', { data: ' ' })
      if (!event.isDefaultPrevented()) {
        applyAction()
        fireFakeInputEvent(editor, 'insertText', { data: ' ' })
      }
    })
  }
  const setup$d = (editor) => {
    editor.on('keydown', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeydownOverride$1(editor, evt)
      }
    })
  }

  const tableTabNavigation = (editor) => {
    if (hasTableTabNavigation(editor)) {
      return [
        {
          keyCode: VK.TAB,
          action: action(handleTab, editor, true),
        },
        {
          keyCode: VK.TAB,
          shiftKey: true,
          action: action(handleTab, editor, false),
        },
      ]
    } else {
      return []
    }
  }
  const executeKeydownOverride = (editor, evt) => {
    execute([...tableTabNavigation(editor)], evt).each((_) => {
      evt.preventDefault()
    })
  }
  const setup$c = (editor) => {
    editor.on('keydown', (evt) => {
      if (evt.isDefaultPrevented() === false) {
        executeKeydownOverride(editor, evt)
      }
    })
  }

  const setup$b = (editor) => {
    editor.addShortcut('Meta+P', '', 'mcePrint')
    setup$j(editor)
    if (isRtc(editor)) {
      return Cell(null)
    } else {
      const caret = setupSelectedState(editor)
      setup$l(editor)
      setup$k(editor, caret)
      setup$i(editor, caret)
      setup$h(editor)
      setup$d(editor)
      setup$f(editor)
      setup$c(editor)
      setup$g(editor, caret)
      setup$e(editor, caret)
      return caret
    }
  }

  class NodeChange {
    constructor(editor) {
      this.lastPath = []
      this.editor = editor
      let lastRng
      const self = this
      if (!('onselectionchange' in editor.getDoc())) {
        editor.on('NodeChange click mouseup keyup focus', (e) => {
          const nativeRng = editor.selection.getRng()
          const fakeRng = {
            startContainer: nativeRng.startContainer,
            startOffset: nativeRng.startOffset,
            endContainer: nativeRng.endContainer,
            endOffset: nativeRng.endOffset,
          }
          if (e.type === 'nodechange' || !isEq$4(fakeRng, lastRng)) {
            editor.dispatch('SelectionChange')
          }
          lastRng = fakeRng
        })
      }
      editor.on('contextmenu', () => {
        editor.dispatch('SelectionChange')
      })
      editor.on('SelectionChange', () => {
        const startElm = editor.selection.getStart(true)
        if (!startElm) {
          return
        }
        if (
          hasAnyRanges(editor) &&
          !self.isSameElementPath(startElm) &&
          editor.dom.isChildOf(startElm, editor.getBody())
        ) {
          editor.nodeChanged({ selectionChange: true })
        }
      })
      editor.on('mouseup', (e) => {
        if (!e.isDefaultPrevented() && hasAnyRanges(editor)) {
          if (editor.selection.getNode().nodeName === 'IMG') {
            Delay.setEditorTimeout(editor, () => {
              editor.nodeChanged()
            })
          } else {
            editor.nodeChanged()
          }
        }
      })
    }
    nodeChanged(args) {
      const selection = this.editor.selection
      let node, parents, root
      if (
        this.editor.initialized &&
        selection &&
        !shouldDisableNodeChange(this.editor) &&
        !this.editor.mode.isReadOnly()
      ) {
        root = this.editor.getBody()
        node = selection.getStart(true) || root
        if (node.ownerDocument !== this.editor.getDoc() || !this.editor.dom.isChildOf(node, root)) {
          node = root
        }
        parents = []
        this.editor.dom.getParent(node, (node) => {
          if (node === root) {
            return true
          }
          parents.push(node)
        })
        args = args || {}
        args.element = node
        args.parents = parents
        this.editor.dispatch('NodeChange', args)
      }
    }
    isSameElementPath(startElm) {
      let i
      const editor = this.editor
      const currentPath = reverse(editor.dom.getParents(startElm, always, editor.getBody()))
      if (currentPath.length === this.lastPath.length) {
        for (i = currentPath.length; i >= 0; i--) {
          if (currentPath[i] !== this.lastPath[i]) {
            break
          }
        }
        if (i === -1) {
          this.lastPath = currentPath
          return true
        }
      }
      this.lastPath = currentPath
      return false
    }
  }

  const internalMimeType = 'x-tinymce/html'
  const internalHtmlMime = constant(internalMimeType)
  const internalMark = '<!-- ' + internalMimeType + ' -->'
  const mark = (html) => internalMark + html
  const unmark = (html) => html.replace(internalMark, '')
  const isMarked = (html) => html.indexOf(internalMark) !== -1

  const isPlainText = (text) => {
    return !/<(?:\/?(?!(?:div|p|br|span)>)\w+|(?:(?!(?:span style="white-space:\s?pre;?">)|br\s?\/>))\w+\s[^>]+)>/i.test(
      text
    )
  }
  const openContainer = (rootTag, rootAttrs) => {
    let tag = '<' + rootTag
    const attrs = mapToArray(rootAttrs, (value, key) => key + '="' + Entities.encodeAllRaw(value) + '"')
    if (attrs.length) {
      tag += ' ' + attrs.join(' ')
    }
    return tag + '>'
  }
  const toBlockElements = (text, rootTag, rootAttrs) => {
    const blocks = text.split(/\n\n/)
    const tagOpen = openContainer(rootTag, rootAttrs)
    const tagClose = '</' + rootTag + '>'
    const paragraphs = map$3(blocks, (p) => {
      return p.split(/\n/).join('<br />')
    })
    const stitch = (p) => {
      return tagOpen + p + tagClose
    }
    return paragraphs.length === 1 ? paragraphs[0] : map$3(paragraphs, stitch).join('')
  }

  const pasteBinDefaultContent = '%MCEPASTEBIN%'
  const create$6 = (editor, lastRngCell) => {
    const { dom, selection } = editor
    const body = editor.getBody()
    lastRngCell.set(selection.getRng())
    const pasteBinElm = dom.add(
      editor.getBody(),
      'div',
      {
        'id': 'mcepastebin',
        'class': 'mce-pastebin',
        'contentEditable': true,
        'data-mce-bogus': 'all',
        'style': 'position: fixed; top: 50%; width: 10px; height: 10px; overflow: hidden; opacity: 0',
      },
      pasteBinDefaultContent
    )
    if (Env.browser.isFirefox()) {
      dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) === 'rtl' ? 65535 : -65535)
    }
    dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', (e) => {
      e.stopPropagation()
    })
    pasteBinElm.focus()
    selection.select(pasteBinElm, true)
  }
  const remove = (editor, lastRngCell) => {
    const dom = editor.dom
    if (getEl(editor)) {
      let pasteBinClone
      const lastRng = lastRngCell.get()
      while ((pasteBinClone = getEl(editor))) {
        dom.remove(pasteBinClone)
        dom.unbind(pasteBinClone)
      }
      if (lastRng) {
        editor.selection.setRng(lastRng)
      }
    }
    lastRngCell.set(null)
  }
  const getEl = (editor) => editor.dom.get('mcepastebin')
  const isPasteBin = (elm) => elm && elm.id === 'mcepastebin'
  const getHtml = (editor) => {
    const dom = editor.dom
    const copyAndRemove = (toElm, fromElm) => {
      toElm.appendChild(fromElm)
      dom.remove(fromElm, true)
    }
    const [pasteBinElm, ...pasteBinClones] = filter$6(editor.getBody().childNodes, isPasteBin)
    each$f(pasteBinClones, (pasteBinClone) => {
      copyAndRemove(pasteBinElm, pasteBinClone)
    })
    const dirtyWrappers = dom.select('div[id=mcepastebin]', pasteBinElm)
    for (let i = dirtyWrappers.length - 1; i >= 0; i--) {
      const cleanWrapper = dom.create('div')
      pasteBinElm.insertBefore(cleanWrapper, dirtyWrappers[i])
      copyAndRemove(cleanWrapper, dirtyWrappers[i])
    }
    return pasteBinElm ? pasteBinElm.innerHTML : ''
  }
  const isDefaultPasteBinContent = (content) => content === pasteBinDefaultContent
  const PasteBin = (editor) => {
    const lastRng = Cell(null)
    return {
      create: () => create$6(editor, lastRng),
      remove: () => remove(editor, lastRng),
      getEl: () => getEl(editor),
      getHtml: () => getHtml(editor),
      getLastRng: lastRng.get,
    }
  }

  const filter = (content, items) => {
    Tools.each(items, (v) => {
      if (is$4(v, RegExp)) {
        content = content.replace(v, '')
      } else {
        content = content.replace(v[0], v[1])
      }
    })
    return content
  }
  const innerText = (html) => {
    const schema = Schema()
    const domParser = DomParser({}, schema)
    let text = ''
    const voidElements = schema.getVoidElements()
    const ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ')
    const blockElements = schema.getBlockElements()
    const walk = (node) => {
      const name = node.name,
        currentNode = node
      if (name === 'br') {
        text += '\n'
        return
      }
      if (name === 'wbr') {
        return
      }
      if (voidElements[name]) {
        text += ' '
      }
      if (ignoreElements[name]) {
        text += ' '
        return
      }
      if (node.type === 3) {
        text += node.value
      }
      if (!(node.name in schema.getVoidElements())) {
        if ((node = node.firstChild)) {
          do {
            walk(node)
          } while ((node = node.next))
        }
      }
      if (blockElements[name] && currentNode.next) {
        text += '\n'
        if (name === 'p') {
          text += '\n'
        }
      }
    }
    html = filter(html, [/<!\[[^\]]+\]>/g])
    walk(domParser.parse(html))
    return text
  }
  const trimHtml = (html) => {
    const trimSpaces = (all, s1, s2) => {
      if (!s1 && !s2) {
        return ' '
      }
      return nbsp
    }
    html = filter(html, [
      /^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/gi,
      /<!--StartFragment-->|<!--EndFragment-->/g,
      [/( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g, trimSpaces],
      /<br class="Apple-interchange-newline">/g,
      /<br>$/i,
    ])
    return html
  }
  const createIdGenerator = (prefix) => {
    let count = 0
    return () => {
      return prefix + count++
    }
  }
  const getImageMimeType = (ext) => {
    const lowerExt = ext.toLowerCase()
    const mimeOverrides = {
      jpg: 'jpeg',
      jpe: 'jpeg',
      jfi: 'jpeg',
      jif: 'jpeg',
      jfif: 'jpeg',
      pjpeg: 'jpeg',
      pjp: 'jpeg',
      svg: 'svg+xml',
    }
    return Tools.hasOwn(mimeOverrides, lowerExt) ? 'image/' + mimeOverrides[lowerExt] : 'image/' + lowerExt
  }

  const preProcess = (editor, html) => {
    const parser = DomParser({}, editor.schema)
    parser.addNodeFilter('meta', (nodes) => {
      Tools.each(nodes, (node) => {
        node.remove()
      })
    })
    const fragment = parser.parse(html, {
      forced_root_block: false,
      isRootContent: true,
    })
    return HtmlSerializer({ validate: true }, editor.schema).serialize(fragment)
  }
  const processResult = (content, cancelled) => ({
    content,
    cancelled,
  })
  const postProcessFilter = (editor, html, internal) => {
    const tempBody = editor.dom.create('div', { style: 'display:none' }, html)
    const postProcessArgs = firePastePostProcess(editor, tempBody, internal)
    return processResult(postProcessArgs.node.innerHTML, postProcessArgs.isDefaultPrevented())
  }
  const filterContent = (editor, content, internal) => {
    const preProcessArgs = firePastePreProcess(editor, content, internal)
    const filteredContent = preProcess(editor, preProcessArgs.content)
    if (editor.hasEventListeners('PastePostProcess') && !preProcessArgs.isDefaultPrevented()) {
      return postProcessFilter(editor, filteredContent, internal)
    } else {
      return processResult(filteredContent, preProcessArgs.isDefaultPrevented())
    }
  }
  const process = (editor, html, internal) => {
    return filterContent(editor, html, internal)
  }

  const pasteHtml$1 = (editor, html) => {
    editor.insertContent(html, {
      merge: shouldPasteMergeFormats(editor),
      paste: true,
    })
    return true
  }
  const isAbsoluteUrl = (url) => /^https?:\/\/[\w\-\/+=.,!;:&%@^~(){}?#]+$/i.test(url)
  const isImageUrl = (editor, url) => {
    return (
      isAbsoluteUrl(url) &&
      exists(getAllowedImageFileTypes(editor), (type) => endsWith(url.toLowerCase(), `.${type.toLowerCase()}`))
    )
  }
  const createImage = (editor, url, pasteHtmlFn) => {
    editor.undoManager.extra(
      () => {
        pasteHtmlFn(editor, url)
      },
      () => {
        editor.insertContent('<img src="' + url + '">')
      }
    )
    return true
  }
  const createLink = (editor, url, pasteHtmlFn) => {
    editor.undoManager.extra(
      () => {
        pasteHtmlFn(editor, url)
      },
      () => {
        editor.execCommand('mceInsertLink', false, url)
      }
    )
    return true
  }
  const linkSelection = (editor, html, pasteHtmlFn) =>
    !editor.selection.isCollapsed() && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtmlFn) : false
  const insertImage = (editor, html, pasteHtmlFn) =>
    isImageUrl(editor, html) ? createImage(editor, html, pasteHtmlFn) : false
  const smartInsertContent = (editor, html) => {
    Tools.each([linkSelection, insertImage, pasteHtml$1], (action) => {
      return action(editor, html, pasteHtml$1) !== true
    })
  }
  const insertContent = (editor, html, pasteAsText) => {
    if (pasteAsText || !isSmartPasteEnabled(editor)) {
      pasteHtml$1(editor, html)
    } else {
      smartInsertContent(editor, html)
    }
  }

  const uniqueId = createIdGenerator('mceclip')
  const doPaste = (editor, content, internal, pasteAsText) => {
    const args = process(editor, content, internal)
    if (args.cancelled === false) {
      insertContent(editor, args.content, pasteAsText)
    }
  }
  const pasteHtml = (editor, html, internalFlag) => {
    const internal = internalFlag ? internalFlag : isMarked(html)
    doPaste(editor, unmark(html), internal, false)
  }
  const pasteText = (editor, text) => {
    const encodedText = editor.dom.encode(text).replace(/\r\n/g, '\n')
    const normalizedText = normalize$4(encodedText, getPasteTabSpaces(editor))
    const html = toBlockElements(normalizedText, getForcedRootBlock(editor), getForcedRootBlockAttrs(editor))
    doPaste(editor, html, false, true)
  }
  const getDataTransferItems = (dataTransfer) => {
    const items = {}
    if (dataTransfer && dataTransfer.types) {
      for (let i = 0; i < dataTransfer.types.length; i++) {
        const contentType = dataTransfer.types[i]
        try {
          items[contentType] = dataTransfer.getData(contentType)
        } catch (ex) {
          items[contentType] = ''
        }
      }
    }
    return items
  }
  const hasContentType = (clipboardContent, mimeType) =>
    mimeType in clipboardContent && clipboardContent[mimeType].length > 0
  const hasHtmlOrText = (content) => hasContentType(content, 'text/html') || hasContentType(content, 'text/plain')
  const extractFilename = (editor, str) => {
    const m = str.match(/([\s\S]+?)(?:\.[a-z0-9.]+)$/i)
    return isNonNullable(m) ? editor.dom.encode(m[1]) : null
  }
  const createBlobInfo = (editor, blobCache, file, base64) => {
    const id = uniqueId()
    const useFileName = shouldReuseFileName(editor) && isNonNullable(file.name)
    const name = useFileName ? extractFilename(editor, file.name) : id
    const filename = useFileName ? file.name : undefined
    const blobInfo = blobCache.create(id, file, base64, name, filename)
    blobCache.add(blobInfo)
    return blobInfo
  }
  const pasteImage = (editor, imageItem) => {
    parseDataUri(imageItem.uri).each(({ data, type, base64Encoded }) => {
      const base64 = base64Encoded ? data : btoa(data)
      const file = imageItem.file
      const blobCache = editor.editorUpload.blobCache
      const existingBlobInfo = blobCache.getByData(base64, type)
      const blobInfo =
        existingBlobInfo !== null && existingBlobInfo !== void 0
          ? existingBlobInfo
          : createBlobInfo(editor, blobCache, file, base64)
      pasteHtml(editor, `<img src="${blobInfo.blobUri()}">`, false)
    })
  }
  const isClipboardEvent = (event) => event.type === 'paste'
  const readFilesAsDataUris = (items) =>
    Promise.all(
      map$3(items, (file) => {
        return blobToDataUri(file).then((uri) => ({
          file,
          uri,
        }))
      })
    )
  const isImage = (editor) => {
    const allowedExtensions = getAllowedImageFileTypes(editor)
    return (file) =>
      startsWith(file.type, 'image/') &&
      exists(allowedExtensions, (extension) => {
        return getImageMimeType(extension) === file.type
      })
  }
  const getImagesFromDataTransfer = (editor, dataTransfer) => {
    const items = dataTransfer.items
      ? bind$3(from(dataTransfer.items), (item) => {
          return item.kind === 'file' ? [item.getAsFile()] : []
        })
      : []
    const files = dataTransfer.files ? from(dataTransfer.files) : []
    return filter$6(items.length > 0 ? items : files, isImage(editor))
  }
  const pasteImageData = (editor, e, rng) => {
    const dataTransfer = isClipboardEvent(e) ? e.clipboardData : e.dataTransfer
    if (shouldPasteDataImages(editor) && dataTransfer) {
      const images = getImagesFromDataTransfer(editor, dataTransfer)
      if (images.length > 0) {
        e.preventDefault()
        readFilesAsDataUris(images).then((fileResults) => {
          if (rng) {
            editor.selection.setRng(rng)
          }
          each$f(fileResults, (result) => {
            pasteImage(editor, result)
          })
        })
        return true
      }
    }
    return false
  }
  const isBrokenAndroidClipboardEvent = (e) => {
    var _a, _b
    return (
      Env.os.isAndroid() &&
      ((_b = (_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.items) === null || _b === void 0
        ? void 0
        : _b.length) === 0
    )
  }
  const isKeyboardPasteEvent = (e) => (VK.metaKeyPressed(e) && e.keyCode === 86) || (e.shiftKey && e.keyCode === 45)
  const insertClipboardContent = (editor, clipboardContent, html, plainTextMode) => {
    let content = trimHtml(html)
    const isInternal = hasContentType(clipboardContent, internalHtmlMime()) || isMarked(html)
    const isPlainTextHtml = !isInternal && isPlainText(content)
    const isAbsoluteUrl$1 = isAbsoluteUrl(content)
    if (isDefaultPasteBinContent(content) || !content.length || (isPlainTextHtml && !isAbsoluteUrl$1)) {
      plainTextMode = true
    }
    if (plainTextMode || isAbsoluteUrl$1) {
      if (hasContentType(clipboardContent, 'text/plain') && isPlainTextHtml) {
        content = clipboardContent['text/plain']
      } else {
        content = innerText(content)
      }
    }
    if (isDefaultPasteBinContent(content)) {
      return
    }
    if (plainTextMode) {
      pasteText(editor, content)
    } else {
      pasteHtml(editor, content, isInternal)
    }
  }
  const registerEventHandlers = (editor, pasteBin, pasteFormat) => {
    let keyboardPastePlainTextState
    const getLastRng = () => pasteBin.getLastRng() || editor.selection.getRng()
    editor.on('keydown', (e) => {
      if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
        keyboardPastePlainTextState = e.shiftKey && e.keyCode === 86
      }
    })
    editor.on('paste', (e) => {
      if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
        return
      }
      const plainTextMode = pasteFormat.get() === 'text' || keyboardPastePlainTextState
      keyboardPastePlainTextState = false
      const clipboardContent = getDataTransferItems(e.clipboardData)
      if (!hasHtmlOrText(clipboardContent) && pasteImageData(editor, e, getLastRng())) {
        return
      }
      if (hasContentType(clipboardContent, 'text/html')) {
        e.preventDefault()
        insertClipboardContent(editor, clipboardContent, clipboardContent['text/html'], plainTextMode)
      } else {
        pasteBin.create()
        Delay.setEditorTimeout(
          editor,
          () => {
            const html = pasteBin.getHtml()
            pasteBin.remove()
            insertClipboardContent(editor, clipboardContent, html, plainTextMode)
          },
          0
        )
      }
    })
  }
  const registerDataImageFilter = (editor) => {
    const isWebKitFakeUrl = (src) => startsWith(src, 'webkit-fake-url')
    const isDataUri = (src) => startsWith(src, 'data:')
    const isPasteInsert = (args) => {
      var _a
      return ((_a = args.data) === null || _a === void 0 ? void 0 : _a.paste) === true
    }
    editor.parser.addNodeFilter('img', (nodes, name, args) => {
      if (!shouldPasteDataImages(editor) && isPasteInsert(args)) {
        for (const node of nodes) {
          const src = node.attr('src')
          if (isString(src) && !node.attr('data-mce-object') && src !== Env.transparentSrc) {
            if (isWebKitFakeUrl(src)) {
              node.remove()
            } else if (!shouldAllowHtmlDataUrls(editor) && isDataUri(src)) {
              node.remove()
            }
          }
        }
      }
    })
  }
  const registerEventsAndFilters = (editor, pasteBin, pasteFormat) => {
    registerEventHandlers(editor, pasteBin, pasteFormat)
    registerDataImageFilter(editor)
  }

  const togglePlainTextPaste = (editor, pasteFormat) => {
    if (pasteFormat.get() === 'text') {
      pasteFormat.set('html')
      firePastePlainTextToggle(editor, false)
    } else {
      pasteFormat.set('text')
      firePastePlainTextToggle(editor, true)
    }
    editor.focus()
  }
  const register$1 = (editor, pasteFormat) => {
    editor.addCommand('mceTogglePlainTextPaste', () => {
      togglePlainTextPaste(editor, pasteFormat)
    })
    editor.addCommand('mceInsertClipboardContent', (ui, value) => {
      if (value.html) {
        pasteHtml(editor, value.html, value.internal)
      }
      if (value.text) {
        pasteText(editor, value.text)
      }
    })
  }

  const setHtml5Clipboard = (clipboardData, html, text) => {
    try {
      clipboardData.clearData()
      clipboardData.setData('text/html', html)
      clipboardData.setData('text/plain', text)
      clipboardData.setData(internalHtmlMime(), html)
      return true
    } catch (e) {
      return false
    }
  }
  const setClipboardData = (evt, data, fallback, done) => {
    if (setHtml5Clipboard(evt.clipboardData, data.html, data.text)) {
      evt.preventDefault()
      done()
    } else {
      fallback(data.html, done)
    }
  }
  const fallback = (editor) => (html, done) => {
    const { dom, selection } = editor
    const outer = dom.create('div', {
      'contenteditable': 'false',
      'data-mce-bogus': 'all',
    })
    const inner = dom.create('div', { contenteditable: 'true' }, html)
    dom.setStyles(outer, {
      position: 'fixed',
      top: '0',
      left: '-3000px',
      width: '1000px',
      overflow: 'hidden',
    })
    outer.appendChild(inner)
    dom.add(editor.getBody(), outer)
    const range = selection.getRng()
    inner.focus()
    const offscreenRange = dom.createRng()
    offscreenRange.selectNodeContents(inner)
    selection.setRng(offscreenRange)
    Delay.setEditorTimeout(
      editor,
      () => {
        selection.setRng(range)
        dom.remove(outer)
        done()
      },
      0
    )
  }
  const getData = (editor) => ({
    html: mark(editor.selection.getContent({ contextual: true })),
    text: editor.selection.getContent({ format: 'text' }),
  })
  const isTableSelection = (editor) =>
    !!editor.dom.getParent(editor.selection.getStart(), 'td[data-mce-selected],th[data-mce-selected]', editor.getBody())
  const hasSelectedContent = (editor) => !editor.selection.isCollapsed() || isTableSelection(editor)
  const cut = (editor) => (evt) => {
    if (!evt.isDefaultPrevented() && hasSelectedContent(editor)) {
      setClipboardData(evt, getData(editor), fallback(editor), () => {
        if (Env.browser.isChromium() || Env.browser.isFirefox()) {
          const rng = editor.selection.getRng()
          Delay.setEditorTimeout(
            editor,
            () => {
              editor.selection.setRng(rng)
              editor.execCommand('Delete')
            },
            0
          )
        } else {
          editor.execCommand('Delete')
        }
      })
    }
  }
  const copy = (editor) => (evt) => {
    if (!evt.isDefaultPrevented() && hasSelectedContent(editor)) {
      setClipboardData(evt, getData(editor), fallback(editor), noop)
    }
  }
  const register = (editor) => {
    editor.on('cut', cut(editor))
    editor.on('copy', copy(editor))
  }

  const getCaretRangeFromEvent = (editor, e) => {
    var _a, _b
    return RangeUtils.getCaretRangeFromPoint(
      (_a = e.clientX) !== null && _a !== void 0 ? _a : 0,
      (_b = e.clientY) !== null && _b !== void 0 ? _b : 0,
      editor.getDoc()
    )
  }
  const isPlainTextFileUrl = (content) => {
    const plainTextContent = content['text/plain']
    return plainTextContent ? plainTextContent.indexOf('file://') === 0 : false
  }
  const setFocusedRange = (editor, rng) => {
    editor.focus()
    if (rng) {
      editor.selection.setRng(rng)
    }
  }
  const hasImage = (dataTransfer) => exists(dataTransfer.files, (file) => /^image\//.test(file.type))
  const setup$a = (editor, draggingInternallyState) => {
    if (shouldPasteBlockDrop(editor)) {
      editor.on('dragend dragover draggesture dragdrop drop drag', (e) => {
        e.preventDefault()
        e.stopPropagation()
      })
    }
    if (!shouldPasteDataImages(editor)) {
      editor.on('drop', (e) => {
        const dataTransfer = e.dataTransfer
        if (dataTransfer && hasImage(dataTransfer)) {
          e.preventDefault()
        }
      })
    }
    editor.on('drop', (e) => {
      if (e.isDefaultPrevented() || draggingInternallyState.get()) {
        return
      }
      const rng = getCaretRangeFromEvent(editor, e)
      if (isNullable(rng)) {
        return
      }
      const dropContent = getDataTransferItems(e.dataTransfer)
      const internal = hasContentType(dropContent, internalHtmlMime())
      if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(editor, e, rng)) {
        return
      }
      const internalContent = dropContent[internalHtmlMime()]
      const content = internalContent || dropContent['text/html'] || dropContent['text/plain']
      if (content) {
        e.preventDefault()
        Delay.setEditorTimeout(editor, () => {
          editor.undoManager.transact(() => {
            if (internalContent) {
              editor.execCommand('Delete')
            }
            setFocusedRange(editor, rng)
            const trimmedContent = trimHtml(content)
            if (dropContent['text/html']) {
              pasteHtml(editor, trimmedContent, internal)
            } else {
              pasteText(editor, trimmedContent)
            }
          })
        })
      }
    })
    editor.on('dragstart', (_e) => {
      draggingInternallyState.set(true)
    })
    editor.on('dragover dragend', (e) => {
      if (shouldPasteDataImages(editor) && draggingInternallyState.get() === false) {
        e.preventDefault()
        setFocusedRange(editor, getCaretRangeFromEvent(editor, e))
      }
      if (e.type === 'dragend') {
        draggingInternallyState.set(false)
      }
    })
  }

  const setup$9 = (editor) => {
    const processEvent = (f) => (e) => {
      f(editor, e)
    }
    const preProcess = getPastePreProcess(editor)
    if (isFunction(preProcess)) {
      editor.on('PastePreProcess', processEvent(preProcess))
    }
    const postProcess = getPastePostProcess(editor)
    if (isFunction(postProcess)) {
      editor.on('PastePostProcess', processEvent(postProcess))
    }
  }

  const addPreProcessFilter = (editor, filterFunc) => {
    editor.on('PastePreProcess', (e) => {
      e.content = filterFunc(editor, e.content, e.internal)
    })
  }
  const rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi
  const rgbToHex = (value) => Tools.trim(value).replace(rgbRegExp, rgbaToHexString).toLowerCase()
  const removeWebKitStyles = (editor, content, internal) => {
    const webKitStylesOption = getPasteWebkitStyles(editor)
    if (internal || webKitStylesOption === 'all' || !shouldPasteRemoveWebKitStyles(editor)) {
      return content
    }
    const webKitStyles = webKitStylesOption ? webKitStylesOption.split(/[, ]/) : []
    if (webKitStyles && webKitStylesOption !== 'none') {
      const dom = editor.dom,
        node = editor.selection.getNode()
      content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, (all, before, value, after) => {
        const inputStyles = dom.parseStyle(dom.decode(value))
        const outputStyles = {}
        for (let i = 0; i < webKitStyles.length; i++) {
          const inputValue = inputStyles[webKitStyles[i]]
          let compareInput = inputValue
          let currentValue = dom.getStyle(node, webKitStyles[i], true)
          if (/color/.test(webKitStyles[i])) {
            compareInput = rgbToHex(compareInput)
            currentValue = rgbToHex(currentValue)
          }
          if (currentValue !== compareInput) {
            outputStyles[webKitStyles[i]] = inputValue
          }
        }
        const outputStyle = dom.serializeStyle(outputStyles, 'span')
        if (outputStyle) {
          return before + ' style="' + outputStyle + '"' + after
        }
        return before + after
      })
    } else {
      content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3')
    }
    content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, (all, before, value, after) => {
      return before + ' style="' + value + '"' + after
    })
    return content
  }
  const setup$8 = (editor) => {
    if (Env.browser.isChromium() || Env.browser.isSafari()) {
      addPreProcessFilter(editor, removeWebKitStyles)
    }
  }

  const setup$7 = (editor) => {
    const draggingInternallyState = Cell(false)
    const pasteFormat = Cell(isPasteAsTextEnabled(editor) ? 'text' : 'html')
    const pasteBin = PasteBin(editor)
    setup$8(editor)
    register$1(editor, pasteFormat)
    setup$9(editor)
    editor.on('PreInit', () => {
      register(editor)
      setup$a(editor, draggingInternallyState)
      registerEventsAndFilters(editor, pasteBin, pasteFormat)
    })
  }

  const preventSummaryToggle = (editor) => {
    editor.on('click', (e) => {
      if (editor.dom.getParent(e.target, 'details')) {
        e.preventDefault()
      }
    })
  }
  const filterDetails = (editor) => {
    editor.parser.addNodeFilter('details', (elms) => {
      each$f(elms, (details) => {
        details.attr('data-mce-open', details.attr('open'))
        details.attr('open', 'open')
      })
    })
    editor.serializer.addNodeFilter('details', (elms) => {
      each$f(elms, (details) => {
        const open = details.attr('data-mce-open')
        details.attr('open', isString(open) ? open : null)
        details.attr('data-mce-open', null)
      })
    })
  }
  const setup$6 = (editor) => {
    preventSummaryToggle(editor)
    filterDetails(editor)
  }

  const isBr = isBr$6
  const isText = isText$9
  const isContentEditableFalse$2 = (elm) => isContentEditableFalse$b(elm.dom)
  const isContentEditableTrue$1 = (elm) => isContentEditableTrue$5(elm.dom)
  const isRoot = (rootNode) => (elm) => eq(SugarElement.fromDom(rootNode), elm)
  const getClosestScope = (node, rootNode) =>
    closest$4(
      SugarElement.fromDom(node),
      (elm) => isContentEditableTrue$1(elm) || isBlock$2(elm),
      isRoot(rootNode)
    ).getOr(SugarElement.fromDom(rootNode)).dom
  const getClosestCef = (node, rootNode) =>
    closest$4(SugarElement.fromDom(node), isContentEditableFalse$2, isRoot(rootNode))
  const findEdgeCaretCandidate = (startNode, scope, forward) => {
    const walker = new DomTreeWalker(startNode, scope)
    const next = forward ? walker.next.bind(walker) : walker.prev.bind(walker)
    let result = startNode
    for (let current = forward ? startNode : next(); current && !isBr(current); current = next()) {
      if (isCaretCandidate$3(current)) {
        result = current
      }
    }
    return result
  }
  const findClosestBlockRange = (startRng, rootNode) => {
    const startPos = CaretPosition.fromRangeStart(startRng)
    const clickNode = startPos.getNode()
    const scope = getClosestScope(clickNode, rootNode)
    const startNode = findEdgeCaretCandidate(clickNode, scope, false)
    const endNode = findEdgeCaretCandidate(clickNode, scope, true)
    const rng = document.createRange()
    getClosestCef(startNode, scope).fold(
      () => {
        if (isText(startNode)) {
          rng.setStart(startNode, 0)
        } else {
          rng.setStartBefore(startNode)
        }
      },
      (cef) => rng.setStartBefore(cef.dom)
    )
    getClosestCef(endNode, scope).fold(
      () => {
        if (isText(endNode)) {
          rng.setEnd(endNode, endNode.data.length)
        } else {
          rng.setEndAfter(endNode)
        }
      },
      (cef) => rng.setEndAfter(cef.dom)
    )
    return rng
  }
  const onTripleClickSelect = (editor) => {
    const rng = findClosestBlockRange(editor.selection.getRng(), editor.getBody())
    editor.selection.setRng(normalize(rng))
  }
  const setup$5 = (editor) => {
    editor.on('mousedown', (e) => {
      if (e.detail >= 3) {
        e.preventDefault()
        onTripleClickSelect(editor)
      }
    })
  }

  var FakeCaretPosition
  ;(function (FakeCaretPosition) {
    FakeCaretPosition['Before'] = 'before'
    FakeCaretPosition['After'] = 'after'
  })(FakeCaretPosition || (FakeCaretPosition = {}))
  const distanceToRectLeft = (clientRect, clientX) => Math.abs(clientRect.left - clientX)
  const distanceToRectRight = (clientRect, clientX) => Math.abs(clientRect.right - clientX)
  const isInsideY = (clientY, clientRect) => clientY >= clientRect.top && clientY <= clientRect.bottom
  const collidesY = (r1, r2) => r1.top < r2.bottom && r1.bottom > r2.top
  const isOverlapping = (r1, r2) => {
    const overlap = overlapY(r1, r2) / Math.min(r1.height, r2.height)
    return collidesY(r1, r2) && overlap > 0.5
  }
  const splitRectsPerAxis = (rects, y) => {
    const intersectingRects = filter$6(rects, (rect) => isInsideY(y, rect))
    return boundingClientRectFromRects(intersectingRects).fold(
      () => [[], rects],
      (boundingRect) => {
        const { pass: horizontal, fail: vertical } = partition$2(rects, (rect) => isOverlapping(rect, boundingRect))
        return [horizontal, vertical]
      }
    )
  }
  const clientInfo = (rect, clientX) => {
    return {
      node: rect.node,
      position:
        distanceToRectLeft(rect, clientX) < distanceToRectRight(rect, clientX)
          ? FakeCaretPosition.Before
          : FakeCaretPosition.After,
    }
  }
  const horizontalDistance = (rect, x, _y) =>
    x > rect.left && x < rect.right ? 0 : Math.min(Math.abs(rect.left - x), Math.abs(rect.right - x))
  const closestChildCaretCandidateNodeRect = (children, clientX, clientY) => {
    const caretCandidateRect = (rect) => {
      if (isCaretCandidate$3(rect.node)) {
        return Optional.some(rect)
      } else if (isElement$6(rect.node)) {
        return closestChildCaretCandidateNodeRect(from(rect.node.childNodes), clientX, clientY)
      } else {
        return Optional.none()
      }
    }
    const getClosestTextNode = (rects, distance) => {
      if (rects.length >= 2) {
        const r1 = caretCandidateRect(rects[0]).getOr(rects[0])
        const r2 = caretCandidateRect(rects[1]).getOr(rects[1])
        const deltaDistance = Math.abs(distance(r1, clientX, clientY) - distance(r2, clientX, clientY))
        if (deltaDistance < 2) {
          if (isText$9(r1.node)) {
            return Optional.some(r1)
          } else if (isText$9(r2.node)) {
            return Optional.some(r2)
          }
        }
      }
      return Optional.none()
    }
    const findClosestCaretCandidateNodeRect = (rects, distance) => {
      const sortedRects = sort(rects, (r1, r2) => distance(r1, clientX, clientY) - distance(r2, clientX, clientY))
      return getClosestTextNode(sortedRects, distance).orThunk(() => findMap(sortedRects, caretCandidateRect))
    }
    const [horizontalRects, verticalRects] = splitRectsPerAxis(getClientRects(children), clientY)
    const { pass: above, fail: below } = partition$2(verticalRects, (rect) => rect.top < clientY)
    return findClosestCaretCandidateNodeRect(horizontalRects, horizontalDistance)
      .orThunk(() => findClosestCaretCandidateNodeRect(below, distanceToRectEdgeFromXY))
      .orThunk(() => findClosestCaretCandidateNodeRect(above, distanceToRectEdgeFromXY))
  }
  const traverseUp = (rootElm, scope, clientX, clientY) => {
    const helper = (scope, prevScope) => {
      return prevScope
        .fold(
          () => closestChildCaretCandidateNodeRect(from(scope.dom.childNodes), clientX, clientY),
          (prevScope) => {
            const uncheckedChildren = filter$6(from(scope.dom.childNodes), (node) => node !== prevScope.dom)
            return closestChildCaretCandidateNodeRect(uncheckedChildren, clientX, clientY)
          }
        )
        .orThunk(() => {
          const parent = eq(scope, rootElm) ? Optional.none() : parentElement(scope)
          return parent.bind((newScope) => helper(newScope, Optional.some(scope)))
        })
    }
    return helper(scope, Optional.none())
  }
  const closestCaretCandidateNodeRect = (root, clientX, clientY) => {
    const rootElm = SugarElement.fromDom(root)
    const ownerDoc = documentOrOwner(rootElm)
    const elementAtPoint = SugarElement.fromPoint(ownerDoc, clientX, clientY).filter((elm) => contains(rootElm, elm))
    const element = elementAtPoint.getOr(rootElm)
    return traverseUp(rootElm, element, clientX, clientY)
  }
  const closestFakeCaretCandidate = (root, clientX, clientY) =>
    closestCaretCandidateNodeRect(root, clientX, clientY)
      .filter((rect) => isFakeCaretTarget(rect.node))
      .map((rect) => clientInfo(rect, clientX))

  const getAbsolutePosition = (elm) => {
    const clientRect = elm.getBoundingClientRect()
    const doc = elm.ownerDocument
    const docElem = doc.documentElement
    const win = doc.defaultView
    return {
      top: clientRect.top + win.pageYOffset - docElem.clientTop,
      left: clientRect.left + win.pageXOffset - docElem.clientLeft,
    }
  }
  const getBodyPosition = (editor) =>
    editor.inline
      ? getAbsolutePosition(editor.getBody())
      : {
          left: 0,
          top: 0,
        }
  const getScrollPosition = (editor) => {
    const body = editor.getBody()
    return editor.inline
      ? {
          left: body.scrollLeft,
          top: body.scrollTop,
        }
      : {
          left: 0,
          top: 0,
        }
  }
  const getBodyScroll = (editor) => {
    const body = editor.getBody(),
      docElm = editor.getDoc().documentElement
    const inlineScroll = {
      left: body.scrollLeft,
      top: body.scrollTop,
    }
    const iframeScroll = {
      left: body.scrollLeft || docElm.scrollLeft,
      top: body.scrollTop || docElm.scrollTop,
    }
    return editor.inline ? inlineScroll : iframeScroll
  }
  const getMousePosition = (editor, event) => {
    if (event.target.ownerDocument !== editor.getDoc()) {
      const iframePosition = getAbsolutePosition(editor.getContentAreaContainer())
      const scrollPosition = getBodyScroll(editor)
      return {
        left: event.pageX - iframePosition.left + scrollPosition.left,
        top: event.pageY - iframePosition.top + scrollPosition.top,
      }
    }
    return {
      left: event.pageX,
      top: event.pageY,
    }
  }
  const calculatePosition = (bodyPosition, scrollPosition, mousePosition) => ({
    pageX: mousePosition.left - bodyPosition.left + scrollPosition.left,
    pageY: mousePosition.top - bodyPosition.top + scrollPosition.top,
  })
  const calc = (editor, event) =>
    calculatePosition(getBodyPosition(editor), getScrollPosition(editor), getMousePosition(editor, event))

  const isContentEditableFalse$1 = isContentEditableFalse$b,
    isContentEditableTrue = isContentEditableTrue$5
  const isDraggable = (rootElm, elm) => isContentEditableFalse$1(elm) && elm !== rootElm
  const isValidDropTarget = (editor, targetElement, dragElement) => {
    if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
      return false
    }
    return !isContentEditableFalse$1(targetElement)
  }
  const cloneElement = (elm) => {
    const cloneElm = elm.cloneNode(true)
    cloneElm.removeAttribute('data-mce-selected')
    return cloneElm
  }
  const createGhost = (editor, elm, width, height) => {
    const dom = editor.dom
    const clonedElm = elm.cloneNode(true)
    dom.setStyles(clonedElm, {
      width,
      height,
    })
    dom.setAttrib(clonedElm, 'data-mce-selected', null)
    const ghostElm = dom.create('div', {
      'class': 'mce-drag-container',
      'data-mce-bogus': 'all',
      'unselectable': 'on',
      'contenteditable': 'false',
    })
    dom.setStyles(ghostElm, {
      position: 'absolute',
      opacity: 0.5,
      overflow: 'hidden',
      border: 0,
      padding: 0,
      margin: 0,
      width,
      height,
    })
    dom.setStyles(clonedElm, {
      margin: 0,
      boxSizing: 'border-box',
    })
    ghostElm.appendChild(clonedElm)
    return ghostElm
  }
  const appendGhostToBody = (ghostElm, bodyElm) => {
    if (ghostElm.parentNode !== bodyElm) {
      bodyElm.appendChild(ghostElm)
    }
  }
  const moveGhost = (ghostElm, position, width, height, maxX, maxY) => {
    let overflowX = 0,
      overflowY = 0
    ghostElm.style.left = position.pageX + 'px'
    ghostElm.style.top = position.pageY + 'px'
    if (position.pageX + width > maxX) {
      overflowX = position.pageX + width - maxX
    }
    if (position.pageY + height > maxY) {
      overflowY = position.pageY + height - maxY
    }
    ghostElm.style.width = width - overflowX + 'px'
    ghostElm.style.height = height - overflowY + 'px'
  }
  const removeElement = (elm) => {
    if (elm && elm.parentNode) {
      elm.parentNode.removeChild(elm)
    }
  }
  const isLeftMouseButtonPressed = (e) => e.button === 0
  const applyRelPos = (state, position) => ({
    pageX: position.pageX - state.relX,
    pageY: position.pageY + 5,
  })
  const start = (state, editor) => (e) => {
    if (isLeftMouseButtonPressed(e)) {
      const ceElm = find$2(editor.dom.getParents(e.target), or(isContentEditableFalse$1, isContentEditableTrue)).getOr(
        null
      )
      if (isDraggable(editor.getBody(), ceElm)) {
        const elmPos = editor.dom.getPos(ceElm)
        const bodyElm = editor.getBody()
        const docElm = editor.getDoc().documentElement
        state.set({
          element: ceElm,
          dragging: false,
          screenX: e.screenX,
          screenY: e.screenY,
          maxX: (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2,
          maxY: (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2,
          relX: e.pageX - elmPos.x,
          relY: e.pageY - elmPos.y,
          width: ceElm.offsetWidth,
          height: ceElm.offsetHeight,
          ghost: createGhost(editor, ceElm, ceElm.offsetWidth, ceElm.offsetHeight),
        })
      }
    }
  }
  const move = (state, editor) => {
    const throttledPlaceCaretAt = first$1((clientX, clientY) => {
      editor._selectionOverrides.hideFakeCaret()
      editor.selection.placeCaretAt(clientX, clientY)
    }, 0)
    editor.on('remove', throttledPlaceCaretAt.cancel)
    return (e) =>
      state.on((state) => {
        const movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY))
        if (!state.dragging && movement > 10) {
          const args = editor.dispatch('dragstart', { target: state.element })
          if (args.isDefaultPrevented()) {
            return
          }
          state.dragging = true
          editor.focus()
        }
        if (state.dragging) {
          const targetPos = applyRelPos(state, calc(editor, e))
          appendGhostToBody(state.ghost, editor.getBody())
          moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY)
          throttledPlaceCaretAt.throttle(e.clientX, e.clientY)
        }
      })
  }
  const getRawTarget = (selection) => {
    const rng = selection.getSel().getRangeAt(0)
    const startContainer = rng.startContainer
    return startContainer.nodeType === 3 ? startContainer.parentNode : startContainer
  }
  const drop = (state, editor) => (e) => {
    state.on((state) => {
      if (state.dragging) {
        if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) {
          const targetClone = cloneElement(state.element)
          const args = editor.dispatch('drop', {
            clientX: e.clientX,
            clientY: e.clientY,
          })
          if (!args.isDefaultPrevented()) {
            editor.undoManager.transact(() => {
              removeElement(state.element)
              editor.insertContent(editor.dom.getOuterHTML(targetClone))
              editor._selectionOverrides.hideFakeCaret()
            })
          }
        }
        editor.dispatch('dragend')
      }
    })
    removeDragState(state)
  }
  const stop = (state, editor) => () => {
    state.on((state) => {
      if (state.dragging) {
        editor.dispatch('dragend')
      }
    })
    removeDragState(state)
  }
  const removeDragState = (state) => {
    state.on((state) => {
      removeElement(state.ghost)
    })
    state.clear()
  }
  const bindFakeDragEvents = (editor) => {
    const state = value$2()
    const pageDom = DOMUtils.DOM
    const rootDocument = document
    const dragStartHandler = start(state, editor)
    const dragHandler = move(state, editor)
    const dropHandler = drop(state, editor)
    const dragEndHandler = stop(state, editor)
    editor.on('mousedown', dragStartHandler)
    editor.on('mousemove', dragHandler)
    editor.on('mouseup', dropHandler)
    pageDom.bind(rootDocument, 'mousemove', dragHandler)
    pageDom.bind(rootDocument, 'mouseup', dragEndHandler)
    editor.on('remove', () => {
      pageDom.unbind(rootDocument, 'mousemove', dragHandler)
      pageDom.unbind(rootDocument, 'mouseup', dragEndHandler)
    })
    editor.on('keydown', (e) => {
      if (e.keyCode === VK.ESC) {
        dragEndHandler()
      }
    })
  }
  const blockUnsupportedFileDrop = (editor) => {
    const preventFileDrop = (e) => {
      if (!e.isDefaultPrevented()) {
        const dataTransfer = e.dataTransfer
        if (dataTransfer && (contains$2(dataTransfer.types, 'Files') || dataTransfer.files.length > 0)) {
          e.preventDefault()
          if (e.type === 'drop') {
            displayError(editor, 'Dropped file type is not supported')
          }
        }
      }
    }
    const preventFileDropIfUIElement = (e) => {
      if (isUIElement(editor, e.target)) {
        preventFileDrop(e)
      }
    }
    const setup = () => {
      const pageDom = DOMUtils.DOM
      const dom = editor.dom
      const doc = document
      const editorRoot = editor.inline ? editor.getBody() : editor.getDoc()
      const eventNames = ['drop', 'dragover']
      each$f(eventNames, (name) => {
        pageDom.bind(doc, name, preventFileDropIfUIElement)
        dom.bind(editorRoot, name, preventFileDrop)
      })
      editor.on('remove', () => {
        each$f(eventNames, (name) => {
          pageDom.unbind(doc, name, preventFileDropIfUIElement)
          dom.unbind(editorRoot, name, preventFileDrop)
        })
      })
    }
    editor.on('init', () => {
      Delay.setEditorTimeout(editor, setup, 0)
    })
  }
  const init$2 = (editor) => {
    bindFakeDragEvents(editor)
    if (shouldBlockUnsupportedDrop(editor)) {
      blockUnsupportedFileDrop(editor)
    }
  }

  const setup$4 = (editor) => {
    const renderFocusCaret = first$1(() => {
      if (!editor.removed && editor.getBody().contains(document.activeElement)) {
        const rng = editor.selection.getRng()
        if (rng.collapsed) {
          const caretRange = renderRangeCaret(editor, rng, false)
          editor.selection.setRng(caretRange)
        }
      }
    }, 0)
    editor.on('focus', () => {
      renderFocusCaret.throttle()
    })
    editor.on('blur', () => {
      renderFocusCaret.cancel()
    })
  }

  const setup$3 = (editor) => {
    editor.on('init', () => {
      editor.on('focusin', (e) => {
        const target = e.target
        if (isMedia$2(target)) {
          const ceRoot = getContentEditableRoot$1(editor.getBody(), target)
          const node = isContentEditableFalse$b(ceRoot) ? ceRoot : target
          if (editor.selection.getNode() !== node) {
            selectNode(editor, node).each((rng) => editor.selection.setRng(rng))
          }
        }
      })
    })
  }

  const isContentEditableFalse = isContentEditableFalse$b
  const getContentEditableRoot = (editor, node) => getContentEditableRoot$1(editor.getBody(), node)
  const SelectionOverrides = (editor) => {
    const selection = editor.selection,
      dom = editor.dom
    const isBlock = dom.isBlock
    const rootNode = editor.getBody()
    const fakeCaret = FakeCaret(editor, rootNode, isBlock, () => hasFocus(editor))
    const realSelectionId = 'sel-' + dom.uniqueId()
    const elementSelectionAttr = 'data-mce-selected'
    let selectedElement
    const isFakeSelectionElement = (node) => dom.hasClass(node, 'mce-offscreen-selection')
    const isFakeSelectionTargetElement = (node) =>
      node !== rootNode && (isContentEditableFalse(node) || isMedia$2(node)) && dom.isChildOf(node, rootNode)
    const setRange = (range) => {
      if (range) {
        selection.setRng(range)
      }
    }
    const showCaret = (direction, node, before, scrollIntoView = true) => {
      const e = editor.dispatch('ShowCaret', {
        target: node,
        direction,
        before,
      })
      if (e.isDefaultPrevented()) {
        return null
      }
      if (scrollIntoView) {
        selection.scrollIntoView(node, direction === -1)
      }
      return fakeCaret.show(before, node)
    }
    const showBlockCaretContainer = (blockCaretContainer) => {
      if (blockCaretContainer.hasAttribute('data-mce-caret')) {
        showCaretContainerBlock(blockCaretContainer)
        selection.scrollIntoView(blockCaretContainer)
      }
    }
    const registerEvents = () => {
      editor.on('click', (e) => {
        const contentEditableRoot = getContentEditableRoot(editor, e.target)
        if (contentEditableRoot) {
          if (isContentEditableFalse(contentEditableRoot)) {
            e.preventDefault()
            editor.focus()
          }
        }
      })
      editor.on('blur NewBlock', removeElementSelection)
      editor.on('ResizeWindow FullscreenStateChanged', fakeCaret.reposition)
      editor.on(
        'tap',
        (e) => {
          const targetElm = e.target
          const contentEditableRoot = getContentEditableRoot(editor, targetElm)
          if (isContentEditableFalse(contentEditableRoot)) {
            e.preventDefault()
            selectNode(editor, contentEditableRoot).each(setElementSelection)
          } else if (isFakeSelectionTargetElement(targetElm)) {
            selectNode(editor, targetElm).each(setElementSelection)
          }
        },
        true
      )
      editor.on('mousedown', (e) => {
        const targetElm = e.target
        if (targetElm !== rootNode && targetElm.nodeName !== 'HTML' && !dom.isChildOf(targetElm, rootNode)) {
          return
        }
        if (isXYInContentArea(editor, e.clientX, e.clientY) === false) {
          return
        }
        removeElementSelection()
        hideFakeCaret()
        const closestContentEditable = getContentEditableRoot(editor, targetElm)
        if (isContentEditableFalse(closestContentEditable)) {
          e.preventDefault()
          selectNode(editor, closestContentEditable).each(setElementSelection)
        } else {
          closestFakeCaretCandidate(rootNode, e.clientX, e.clientY).each((caretInfo) => {
            e.preventDefault()
            const range = showCaret(1, caretInfo.node, caretInfo.position === FakeCaretPosition.Before, false)
            setRange(range)
            if (isElement$6(closestContentEditable)) {
              closestContentEditable.focus()
            } else {
              editor.getBody().focus()
            }
          })
        }
      })
      editor.on('keypress', (e) => {
        if (VK.modifierPressed(e)) {
          return
        }
        if (isContentEditableFalse(selection.getNode())) {
          e.preventDefault()
        }
      })
      editor.on('GetSelectionRange', (e) => {
        let rng = e.range
        if (selectedElement) {
          if (!selectedElement.parentNode) {
            selectedElement = null
            return
          }
          rng = rng.cloneRange()
          rng.selectNode(selectedElement)
          e.range = rng
        }
      })
      editor.on('SetSelectionRange', (e) => {
        e.range = normalizeVoidElementSelection(e.range)
        const rng = setElementSelection(e.range, e.forward)
        if (rng) {
          e.range = rng
        }
      })
      const isPasteBin = (node) => node.id === 'mcepastebin'
      editor.on('AfterSetSelectionRange', (e) => {
        const rng = e.range
        const parentNode = rng.startContainer.parentNode
        if (!isRangeInCaretContainer(rng) && !isPasteBin(parentNode)) {
          hideFakeCaret()
        }
        if (!isFakeSelectionElement(parentNode)) {
          removeElementSelection()
        }
      })
      init$2(editor)
      setup$4(editor)
      setup$3(editor)
    }
    const isWithinCaretContainer = (node) =>
      isCaretContainer$2(node) || startsWithCaretContainer$1(node) || endsWithCaretContainer$1(node)
    const isRangeInCaretContainer = (rng) =>
      isWithinCaretContainer(rng.startContainer) || isWithinCaretContainer(rng.endContainer)
    const normalizeVoidElementSelection = (rng) => {
      const voidElements = editor.schema.getVoidElements()
      const newRng = dom.createRng()
      const startContainer = rng.startContainer
      const startOffset = rng.startOffset
      const endContainer = rng.endContainer
      const endOffset = rng.endOffset
      if (has$2(voidElements, startContainer.nodeName.toLowerCase())) {
        if (startOffset === 0) {
          newRng.setStartBefore(startContainer)
        } else {
          newRng.setStartAfter(startContainer)
        }
      } else {
        newRng.setStart(startContainer, startOffset)
      }
      if (has$2(voidElements, endContainer.nodeName.toLowerCase())) {
        if (endOffset === 0) {
          newRng.setEndBefore(endContainer)
        } else {
          newRng.setEndAfter(endContainer)
        }
      } else {
        newRng.setEnd(endContainer, endOffset)
      }
      return newRng
    }
    const setupOffscreenSelection = (node, targetClone) => {
      const body = SugarElement.fromDom(editor.getBody())
      const doc = editor.getDoc()
      const realSelectionContainer = descendant(body, '#' + realSelectionId).getOrThunk(() => {
        const newContainer = SugarElement.fromHtml(
          '<div data-mce-bogus="all" class="mce-offscreen-selection"></div>',
          doc
        )
        set$2(newContainer, 'id', realSelectionId)
        append$1(body, newContainer)
        return newContainer
      })
      const newRange = dom.createRng()
      empty(realSelectionContainer)
      append(realSelectionContainer, [
        SugarElement.fromText(nbsp, doc),
        SugarElement.fromDom(targetClone),
        SugarElement.fromText(nbsp, doc),
      ])
      newRange.setStart(realSelectionContainer.dom.firstChild, 1)
      newRange.setEnd(realSelectionContainer.dom.lastChild, 0)
      setAll(realSelectionContainer, { top: dom.getPos(node, editor.getBody()).y + 'px' })
      focus$1(realSelectionContainer)
      const sel = selection.getSel()
      sel.removeAllRanges()
      sel.addRange(newRange)
      return newRange
    }
    const selectElement = (elm) => {
      const targetClone = elm.cloneNode(true)
      const e = editor.dispatch('ObjectSelected', {
        target: elm,
        targetClone,
      })
      if (e.isDefaultPrevented()) {
        return null
      }
      const range = setupOffscreenSelection(elm, e.targetClone)
      const nodeElm = SugarElement.fromDom(elm)
      each$f(descendants(SugarElement.fromDom(editor.getBody()), `*[${elementSelectionAttr}]`), (elm) => {
        if (!eq(nodeElm, elm)) {
          remove$b(elm, elementSelectionAttr)
        }
      })
      if (!dom.getAttrib(elm, elementSelectionAttr)) {
        elm.setAttribute(elementSelectionAttr, '1')
      }
      selectedElement = elm
      hideFakeCaret()
      return range
    }
    const setElementSelection = (range, forward) => {
      if (!range) {
        return null
      }
      if (range.collapsed) {
        if (!isRangeInCaretContainer(range)) {
          const dir = forward ? 1 : -1
          const caretPosition = getNormalizedRangeEndPoint(dir, rootNode, range)
          const beforeNode = caretPosition.getNode(!forward)
          if (isFakeCaretTarget(beforeNode)) {
            return showCaret(dir, beforeNode, forward ? !caretPosition.isAtEnd() : false, false)
          }
          const afterNode = caretPosition.getNode(forward)
          if (isFakeCaretTarget(afterNode)) {
            return showCaret(dir, afterNode, forward ? false : !caretPosition.isAtEnd(), false)
          }
        }
        return null
      }
      let startContainer = range.startContainer
      let startOffset = range.startOffset
      const endOffset = range.endOffset
      if (startContainer.nodeType === 3 && startOffset === 0 && isContentEditableFalse(startContainer.parentNode)) {
        startContainer = startContainer.parentNode
        startOffset = dom.nodeIndex(startContainer)
        startContainer = startContainer.parentNode
      }
      if (startContainer.nodeType !== 1) {
        return null
      }
      if (endOffset === startOffset + 1 && startContainer === range.endContainer) {
        const node = startContainer.childNodes[startOffset]
        if (isFakeSelectionTargetElement(node)) {
          return selectElement(node)
        }
      }
      return null
    }
    const removeElementSelection = () => {
      if (selectedElement) {
        selectedElement.removeAttribute(elementSelectionAttr)
      }
      descendant(SugarElement.fromDom(editor.getBody()), '#' + realSelectionId).each(remove$6)
      selectedElement = null
    }
    const destroy = () => {
      fakeCaret.destroy()
      selectedElement = null
    }
    const hideFakeCaret = () => {
      fakeCaret.hide()
    }
    if (!isRtc(editor)) {
      registerEvents()
    }
    return {
      showCaret,
      showBlockCaretContainer,
      hideFakeCaret,
      destroy,
    }
  }

  const generatePath = (root, node, offset) => {
    if (isText$9(node) && (offset < 0 || offset > node.data.length)) {
      return []
    }
    const p = [offset]
    let current = node
    while (current !== root && current.parentNode) {
      const parent = current.parentNode
      for (let i = 0; i < parent.childNodes.length; i++) {
        if (parent.childNodes[i] === current) {
          p.push(i)
          break
        }
      }
      current = parent
    }
    return current === root ? p.reverse() : []
  }
  const generatePathRange = (root, startNode, startOffset, endNode, endOffset) => {
    const start = generatePath(root, startNode, startOffset)
    const end = generatePath(root, endNode, endOffset)
    return {
      start,
      end,
    }
  }
  const resolvePath = (root, path) => {
    const nodePath = path.slice()
    const offset = nodePath.pop()
    const resolvedNode = foldl(
      nodePath,
      (optNode, index) => optNode.bind((node) => Optional.from(node.childNodes[index])),
      Optional.some(root)
    )
    return resolvedNode.bind((node) => {
      if (isText$9(node) && (offset < 0 || offset > node.data.length)) {
        return Optional.none()
      } else {
        return Optional.some({
          node,
          offset,
        })
      }
    })
  }
  const resolvePathRange = (root, range) =>
    resolvePath(root, range.start).bind(({ node: startNode, offset: startOffset }) =>
      resolvePath(root, range.end).map(({ node: endNode, offset: endOffset }) => {
        const rng = document.createRange()
        rng.setStart(startNode, startOffset)
        rng.setEnd(endNode, endOffset)
        return rng
      })
    )
  const generatePathRangeFromRange = (root, range) =>
    generatePathRange(root, range.startContainer, range.startOffset, range.endContainer, range.endOffset)

  const cleanEmptyNodes = (dom, node, isRoot) => {
    if (node && dom.isEmpty(node) && !isRoot(node)) {
      const parent = node.parentNode
      dom.remove(node)
      cleanEmptyNodes(dom, parent, isRoot)
    }
  }
  const deleteRng = (dom, rng, isRoot, clean = true) => {
    const startParent = rng.startContainer.parentNode
    const endParent = rng.endContainer.parentNode
    rng.deleteContents()
    if (clean && !isRoot(rng.startContainer)) {
      if (isText$9(rng.startContainer) && rng.startContainer.data.length === 0) {
        dom.remove(rng.startContainer)
      }
      if (isText$9(rng.endContainer) && rng.endContainer.data.length === 0) {
        dom.remove(rng.endContainer)
      }
      cleanEmptyNodes(dom, startParent, isRoot)
      if (startParent !== endParent) {
        cleanEmptyNodes(dom, endParent, isRoot)
      }
    }
  }
  const getParentBlock = (editor, rng) => Optional.from(editor.dom.getParent(rng.startContainer, editor.dom.isBlock))

  const stripPattern = (dom, block, pattern) => {
    const firstTextNode = textAfter(block, 0, block)
    firstTextNode.each((spot) => {
      const node = spot.container
      scanRight(node, pattern.start.length, block).each((end) => {
        const rng = dom.createRng()
        rng.setStart(node, 0)
        rng.setEnd(end.container, end.offset)
        deleteRng(dom, rng, (e) => e === block)
      })
    })
  }
  const applyPattern$1 = (editor, match) => {
    const dom = editor.dom
    const pattern = match.pattern
    const rng = resolvePathRange(dom.getRoot(), match.range).getOrDie('Unable to resolve path range')
    const isBlockFormatName = (name, formatter) => {
      const formatSet = formatter.get(name)
      return isArray$1(formatSet) && head(formatSet).exists((format) => has$2(format, 'block'))
    }
    getParentBlock(editor, rng).each((block) => {
      if (pattern.type === 'block-format') {
        if (isBlockFormatName(pattern.format, editor.formatter)) {
          editor.undoManager.transact(() => {
            stripPattern(editor.dom, block, pattern)
            editor.formatter.apply(pattern.format)
          })
        }
      } else if (pattern.type === 'block-command') {
        editor.undoManager.transact(() => {
          stripPattern(editor.dom, block, pattern)
          editor.execCommand(pattern.cmd, false, pattern.value)
        })
      }
    })
    return true
  }
  const findPattern$1 = (patterns, text) => {
    const nuText = text.replace(nbsp, ' ')
    return find$2(patterns, (pattern) => text.indexOf(pattern.start) === 0 || nuText.indexOf(pattern.start) === 0)
  }
  const findPatterns$1 = (editor, patterns) => {
    const dom = editor.dom
    const rng = editor.selection.getRng()
    return getParentBlock(editor, rng)
      .filter((block) => {
        const forcedRootBlock = getForcedRootBlock(editor)
        const matchesForcedRootBlock = dom.is(block, forcedRootBlock)
        return block !== null && matchesForcedRootBlock
      })
      .bind((block) => {
        const blockText = block.textContent
        const matchedPattern = findPattern$1(patterns, blockText)
        return matchedPattern.map((pattern) => {
          if (Tools.trim(blockText).length === pattern.start.length) {
            return []
          }
          return [
            {
              pattern,
              range: generatePathRange(dom.getRoot(), block, 0, block, 0),
            },
          ]
        })
      })
      .getOr([])
  }
  const applyMatches$1 = (editor, matches) => {
    if (matches.length === 0) {
      return
    }
    const bookmark = editor.selection.getBookmark()
    each$f(matches, (match) => applyPattern$1(editor, match))
    editor.selection.moveToBookmark(bookmark)
  }

  const newMarker = (dom, id) =>
    dom.create('span', {
      'data-mce-type': 'bookmark',
      id,
    })
  const rangeFromMarker = (dom, marker) => {
    const rng = dom.createRng()
    rng.setStartAfter(marker.start)
    rng.setEndBefore(marker.end)
    return rng
  }
  const createMarker = (dom, markerPrefix, pathRange) => {
    const rng = resolvePathRange(dom.getRoot(), pathRange).getOrDie('Unable to resolve path range')
    const startNode = rng.startContainer
    const endNode = rng.endContainer
    const textEnd = rng.endOffset === 0 ? endNode : endNode.splitText(rng.endOffset)
    const textStart = rng.startOffset === 0 ? startNode : startNode.splitText(rng.startOffset)
    return {
      prefix: markerPrefix,
      end: textEnd.parentNode.insertBefore(newMarker(dom, markerPrefix + '-end'), textEnd),
      start: textStart.parentNode.insertBefore(newMarker(dom, markerPrefix + '-start'), textStart),
    }
  }
  const removeMarker = (dom, marker, isRoot) => {
    cleanEmptyNodes(dom, dom.get(marker.prefix + '-end'), isRoot)
    cleanEmptyNodes(dom, dom.get(marker.prefix + '-start'), isRoot)
  }

  const isReplacementPattern = (pattern) => pattern.start.length === 0
  const matchesPattern = (patternContent) => (element, offset) => {
    const text = element.data
    const searchText = text.substring(0, offset)
    const startEndIndex = searchText.lastIndexOf(patternContent.charAt(patternContent.length - 1))
    const startIndex = searchText.lastIndexOf(patternContent)
    if (startIndex !== -1) {
      return startIndex + patternContent.length
    } else if (startEndIndex !== -1) {
      return startEndIndex + 1
    } else {
      return -1
    }
  }
  const findPatternStartFromSpot = (dom, pattern, block, spot) => {
    const startPattern = pattern.start
    const startSpot = repeatLeft(dom, spot.container, spot.offset, matchesPattern(startPattern), block)
    return startSpot.bind((spot) => {
      if (spot.offset >= startPattern.length) {
        const rng = dom.createRng()
        rng.setStart(spot.container, spot.offset - startPattern.length)
        rng.setEnd(spot.container, spot.offset)
        return Optional.some(rng)
      } else {
        const offset = spot.offset - startPattern.length
        return scanLeft(spot.container, offset, block)
          .map((nextSpot) => {
            const rng = dom.createRng()
            rng.setStart(nextSpot.container, nextSpot.offset)
            rng.setEnd(spot.container, spot.offset)
            return rng
          })
          .filter((rng) => rng.toString() === startPattern)
          .orThunk(() => findPatternStartFromSpot(dom, pattern, block, point(spot.container, 0)))
      }
    })
  }
  const findPatternStart = (dom, pattern, node, offset, block, requireGap = false) => {
    if (pattern.start.length === 0 && !requireGap) {
      const rng = dom.createRng()
      rng.setStart(node, offset)
      rng.setEnd(node, offset)
      return Optional.some(rng)
    }
    return textBefore(node, offset, block).bind((spot) => {
      const start = findPatternStartFromSpot(dom, pattern, block, spot)
      return start.bind((startRange) => {
        if (requireGap) {
          if (startRange.endContainer === spot.container && startRange.endOffset === spot.offset) {
            return Optional.none()
          } else if (spot.offset === 0 && startRange.endContainer.textContent.length === startRange.endOffset) {
            return Optional.none()
          }
        }
        return Optional.some(startRange)
      })
    })
  }
  const findPattern = (editor, block, details) => {
    const dom = editor.dom
    const root = dom.getRoot()
    const pattern = details.pattern
    const endNode = details.position.container
    const endOffset = details.position.offset
    return scanLeft(endNode, endOffset - details.pattern.end.length, block).bind((spot) => {
      const endPathRng = generatePathRange(root, spot.container, spot.offset, endNode, endOffset)
      if (isReplacementPattern(pattern)) {
        return Optional.some({
          matches: [
            {
              pattern,
              startRng: endPathRng,
              endRng: endPathRng,
            },
          ],
          position: spot,
        })
      } else {
        const resultsOpt = findPatternsRec(editor, details.remainingPatterns, spot.container, spot.offset, block)
        const results = resultsOpt.getOr({
          matches: [],
          position: spot,
        })
        const pos = results.position
        const start = findPatternStart(dom, pattern, pos.container, pos.offset, block, resultsOpt.isNone())
        return start.map((startRng) => {
          const startPathRng = generatePathRangeFromRange(root, startRng)
          return {
            matches: results.matches.concat([
              {
                pattern,
                startRng: startPathRng,
                endRng: endPathRng,
              },
            ]),
            position: point(startRng.startContainer, startRng.startOffset),
          }
        })
      }
    })
  }
  const findPatternsRec = (editor, patterns, node, offset, block) => {
    const dom = editor.dom
    return textBefore(node, offset, dom.getRoot()).bind((endSpot) => {
      const rng = dom.createRng()
      rng.setStart(block, 0)
      rng.setEnd(node, offset)
      const text = rng.toString()
      for (let i = 0; i < patterns.length; i++) {
        const pattern = patterns[i]
        if (!endsWith(text, pattern.end)) {
          continue
        }
        const patternsWithoutCurrent = patterns.slice()
        patternsWithoutCurrent.splice(i, 1)
        const result = findPattern(editor, block, {
          pattern,
          remainingPatterns: patternsWithoutCurrent,
          position: endSpot,
        })
        if (result.isSome()) {
          return result
        }
      }
      return Optional.none()
    })
  }
  const applyPattern = (editor, pattern, patternRange) => {
    editor.selection.setRng(patternRange)
    if (pattern.type === 'inline-format') {
      each$f(pattern.format, (format) => {
        editor.formatter.apply(format)
      })
    } else {
      editor.execCommand(pattern.cmd, false, pattern.value)
    }
  }
  const applyReplacementPattern = (editor, pattern, marker, isRoot) => {
    const markerRange = rangeFromMarker(editor.dom, marker)
    deleteRng(editor.dom, markerRange, isRoot)
    applyPattern(editor, pattern, markerRange)
  }
  const applyPatternWithContent = (editor, pattern, startMarker, endMarker, isRoot) => {
    const dom = editor.dom
    const markerEndRange = rangeFromMarker(dom, endMarker)
    const markerStartRange = rangeFromMarker(dom, startMarker)
    deleteRng(dom, markerStartRange, isRoot)
    deleteRng(dom, markerEndRange, isRoot)
    const patternMarker = {
      prefix: startMarker.prefix,
      start: startMarker.end,
      end: endMarker.start,
    }
    const patternRange = rangeFromMarker(dom, patternMarker)
    applyPattern(editor, pattern, patternRange)
  }
  const addMarkers = (dom, matches) => {
    const markerPrefix = generate$1('mce_textpattern')
    const matchesWithEnds = foldr(
      matches,
      (acc, match) => {
        const endMarker = createMarker(dom, markerPrefix + `_end${acc.length}`, match.endRng)
        return acc.concat([
          {
            ...match,
            endMarker,
          },
        ])
      },
      []
    )
    return foldr(
      matchesWithEnds,
      (acc, match) => {
        const idx = matchesWithEnds.length - acc.length - 1
        const startMarker = isReplacementPattern(match.pattern)
          ? match.endMarker
          : createMarker(dom, markerPrefix + `_start${idx}`, match.startRng)
        return acc.concat([
          {
            ...match,
            startMarker,
          },
        ])
      },
      []
    )
  }
  const findPatterns = (editor, patterns, space) => {
    const rng = editor.selection.getRng()
    if (rng.collapsed === false) {
      return []
    }
    return getParentBlock(editor, rng)
      .bind((block) => {
        const offset = Math.max(0, rng.startOffset - (space ? 1 : 0))
        return findPatternsRec(editor, patterns, rng.startContainer, offset, block)
      })
      .fold(
        () => [],
        (result) => result.matches
      )
  }
  const applyMatches = (editor, matches) => {
    if (matches.length === 0) {
      return
    }
    const dom = editor.dom
    const bookmark = editor.selection.getBookmark()
    const matchesWithMarkers = addMarkers(dom, matches)
    each$f(matchesWithMarkers, (match) => {
      const block = dom.getParent(match.startMarker.start, dom.isBlock)
      const isRoot = (node) => node === block
      if (isReplacementPattern(match.pattern)) {
        applyReplacementPattern(editor, match.pattern, match.endMarker, isRoot)
      } else {
        applyPatternWithContent(editor, match.pattern, match.startMarker, match.endMarker, isRoot)
      }
      removeMarker(dom, match.endMarker, isRoot)
      removeMarker(dom, match.startMarker, isRoot)
    })
    editor.selection.moveToBookmark(bookmark)
  }

  const hasPatterns = (patternSet) => patternSet.inlinePatterns.length > 0 || patternSet.blockPatterns.length > 0
  const handleEnter = (editor, patternSet) => {
    if (!editor.selection.isCollapsed() || !hasPatterns(patternSet)) {
      return false
    }
    const inlineMatches = findPatterns(editor, patternSet.inlinePatterns, false)
    const blockMatches = findPatterns$1(editor, patternSet.blockPatterns)
    if (blockMatches.length > 0 || inlineMatches.length > 0) {
      editor.undoManager.add()
      editor.undoManager.extra(
        () => {
          editor.execCommand('mceInsertNewLine')
        },
        () => {
          editor.insertContent(zeroWidth)
          applyMatches(editor, inlineMatches)
          applyMatches$1(editor, blockMatches)
          const range = editor.selection.getRng()
          const spot = textBefore(range.startContainer, range.startOffset, editor.dom.getRoot())
          editor.execCommand('mceInsertNewLine')
          spot.each((s) => {
            const node = s.container
            if (node.data.charAt(s.offset - 1) === zeroWidth) {
              node.deleteData(s.offset - 1, 1)
              cleanEmptyNodes(editor.dom, node.parentNode, (e) => e === editor.dom.getRoot())
            }
          })
        }
      )
      return true
    }
    return false
  }
  const handleInlineKey = (editor, inlinePatterns) => {
    if (inlinePatterns.length > 0) {
      const inlineMatches = findPatterns(editor, inlinePatterns, true)
      if (inlineMatches.length > 0) {
        editor.undoManager.transact(() => {
          applyMatches(editor, inlineMatches)
        })
      }
    }
  }
  const checkKeyEvent = (codes, event, predicate) => {
    for (let i = 0; i < codes.length; i++) {
      if (predicate(codes[i], event)) {
        return true
      }
    }
    return false
  }
  const checkKeyCode = (codes, event) =>
    checkKeyEvent(codes, event, (code, event) => {
      return code === event.keyCode && VK.modifierPressed(event) === false
    })
  const checkCharCode = (chars, event) =>
    checkKeyEvent(chars, event, (chr, event) => {
      return chr.charCodeAt(0) === event.charCode
    })

  const setup$2 = (editor) => {
    const charCodes = [',', '.', ';', ':', '!', '?']
    const keyCodes = [32]
    const getPatternSet = () => createPatternSet(getTextPatterns(editor))
    const getInlinePatterns$1 = () => getInlinePatterns(getTextPatterns(editor))
    editor.on(
      'keydown',
      (e) => {
        if (e.keyCode === 13 && !VK.modifierPressed(e)) {
          if (handleEnter(editor, getPatternSet())) {
            e.preventDefault()
          }
        }
      },
      true
    )
    editor.on('keyup', (e) => {
      if (checkKeyCode(keyCodes, e)) {
        handleInlineKey(editor, getInlinePatterns$1())
      }
    })
    editor.on('keypress', (e) => {
      if (checkCharCode(charCodes, e)) {
        Delay.setEditorTimeout(editor, () => {
          handleInlineKey(editor, getInlinePatterns$1())
        })
      }
    })
  }

  const setup$1 = (editor) => {
    setup$2(editor)
  }

  const Quirks = (editor) => {
    const each = Tools.each
    const BACKSPACE = VK.BACKSPACE,
      DELETE = VK.DELETE,
      dom = editor.dom,
      selection = editor.selection,
      parser = editor.parser
    const browser = Env.browser
    const isGecko = browser.isFirefox()
    const isWebKit = browser.isChromium() || browser.isSafari()
    const isiOS = Env.deviceType.isiPhone() || Env.deviceType.isiPad()
    const isMac = Env.os.isMacOS() || Env.os.isiOS()
    const setEditorCommandState = (cmd, state) => {
      try {
        editor.getDoc().execCommand(cmd, false, state)
      } catch (ex) {}
    }
    const isDefaultPrevented = (e) => {
      return e.isDefaultPrevented()
    }
    const emptyEditorWhenDeleting = () => {
      const serializeRng = (rng) => {
        const body = dom.create('body')
        const contents = rng.cloneContents()
        body.appendChild(contents)
        return selection.serializer.serialize(body, { format: 'html' })
      }
      const allContentsSelected = (rng) => {
        const selection = serializeRng(rng)
        const allRng = dom.createRng()
        allRng.selectNode(editor.getBody())
        const allSelection = serializeRng(allRng)
        return selection === allSelection
      }
      editor.on('keydown', (e) => {
        const keyCode = e.keyCode
        let isCollapsed, body
        if (!isDefaultPrevented(e) && (keyCode === DELETE || keyCode === BACKSPACE)) {
          isCollapsed = editor.selection.isCollapsed()
          body = editor.getBody()
          if (isCollapsed && !dom.isEmpty(body)) {
            return
          }
          if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
            return
          }
          e.preventDefault()
          editor.setContent('')
          if (body.firstChild && dom.isBlock(body.firstChild)) {
            editor.selection.setCursorLocation(body.firstChild, 0)
          } else {
            editor.selection.setCursorLocation(body, 0)
          }
          editor.nodeChanged()
        }
      })
    }
    const selectAll = () => {
      editor.shortcuts.add('meta+a', null, 'SelectAll')
    }
    const documentElementEditingFocus = () => {
      if (!editor.inline) {
        dom.bind(editor.getDoc(), 'mousedown mouseup', (e) => {
          let rng
          if (e.target === editor.getDoc().documentElement) {
            rng = selection.getRng()
            editor.getBody().focus()
            if (e.type === 'mousedown') {
              if (isCaretContainer$2(rng.startContainer)) {
                return
              }
              selection.placeCaretAt(e.clientX, e.clientY)
            } else {
              selection.setRng(rng)
            }
          }
        })
      }
    }
    const removeHrOnBackspace = () => {
      editor.on('keydown', (e) => {
        if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
          if (!editor.getBody().getElementsByTagName('hr').length) {
            return
          }
          if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
            const node = selection.getNode()
            const previousSibling = node.previousSibling
            if (node.nodeName === 'HR') {
              dom.remove(node)
              e.preventDefault()
              return
            }
            if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'hr') {
              dom.remove(previousSibling)
              e.preventDefault()
            }
          }
        }
      })
    }
    const focusBody = () => {
      if (!Range.prototype.getClientRects) {
        editor.on('mousedown', (e) => {
          if (!isDefaultPrevented(e) && e.target.nodeName === 'HTML') {
            const body = editor.getBody()
            body.blur()
            Delay.setEditorTimeout(editor, () => {
              body.focus()
            })
          }
        })
      }
    }
    const selectControlElements = () => {
      const visualAidsAnchorClass = getVisualAidsAnchorClass(editor)
      editor.on('click', (e) => {
        const target = e.target
        if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== 'false') {
          e.preventDefault()
          editor.selection.select(target)
          editor.nodeChanged()
        }
        if (target.nodeName === 'A' && dom.hasClass(target, visualAidsAnchorClass) && target.childNodes.length === 0) {
          e.preventDefault()
          selection.select(target)
        }
      })
    }
    const removeStylesWhenDeletingAcrossBlockElements = () => {
      const getAttributeApplyFunction = () => {
        const template = dom.getAttribs(selection.getStart().cloneNode(false))
        return () => {
          const target = selection.getStart()
          if (target !== editor.getBody()) {
            dom.setAttrib(target, 'style', null)
            each(template, (attr) => {
              target.setAttributeNode(attr.cloneNode(true))
            })
          }
        }
      }
      const isSelectionAcrossElements = () => {
        return (
          !selection.isCollapsed() &&
          dom.getParent(selection.getStart(), dom.isBlock) !== dom.getParent(selection.getEnd(), dom.isBlock)
        )
      }
      editor.on('keypress', (e) => {
        let applyAttributes
        if (!isDefaultPrevented(e) && (e.keyCode === 8 || e.keyCode === 46) && isSelectionAcrossElements()) {
          applyAttributes = getAttributeApplyFunction()
          editor.getDoc().execCommand('delete', false, null)
          applyAttributes()
          e.preventDefault()
          return false
        }
      })
      dom.bind(editor.getDoc(), 'cut', (e) => {
        let applyAttributes
        if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
          applyAttributes = getAttributeApplyFunction()
          Delay.setEditorTimeout(editor, () => {
            applyAttributes()
          })
        }
      })
    }
    const disableBackspaceIntoATable = () => {
      editor.on('keydown', (e) => {
        if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
          if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
            const previousSibling = selection.getNode().previousSibling
            if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'table') {
              e.preventDefault()
              return false
            }
          }
        }
      })
    }
    const removeBlockQuoteOnBackSpace = () => {
      editor.on('keydown', (e) => {
        let rng, parent
        if (isDefaultPrevented(e) || e.keyCode !== VK.BACKSPACE) {
          return
        }
        rng = selection.getRng()
        const container = rng.startContainer
        const offset = rng.startOffset
        const root = dom.getRoot()
        parent = container
        if (!rng.collapsed || offset !== 0) {
          return
        }
        while (parent && parent.parentNode && parent.parentNode.firstChild === parent && parent.parentNode !== root) {
          parent = parent.parentNode
        }
        if (parent.tagName === 'BLOCKQUOTE') {
          editor.formatter.toggle('blockquote', null, parent)
          rng = dom.createRng()
          rng.setStart(container, 0)
          rng.setEnd(container, 0)
          selection.setRng(rng)
        }
      })
    }
    const setGeckoEditingOptions = () => {
      const setOpts = () => {
        setEditorCommandState('StyleWithCSS', false)
        setEditorCommandState('enableInlineTableEditing', false)
        if (!getObjectResizing(editor)) {
          setEditorCommandState('enableObjectResizing', false)
        }
      }
      if (!isReadOnly$1(editor)) {
        editor.on('BeforeExecCommand mousedown', setOpts)
      }
    }
    const addBrAfterLastLinks = () => {
      const fixLinks = () => {
        each(dom.select('a'), (node) => {
          let parentNode = node.parentNode
          const root = dom.getRoot()
          if (parentNode.lastChild === node) {
            while (parentNode && !dom.isBlock(parentNode)) {
              if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
                return
              }
              parentNode = parentNode.parentNode
            }
            dom.add(parentNode, 'br', { 'data-mce-bogus': 1 })
          }
        })
      }
      editor.on('SetContent ExecCommand', (e) => {
        if (e.type === 'setcontent' || e.command === 'mceInsertLink') {
          fixLinks()
        }
      })
    }
    const setDefaultBlockType = () => {
      editor.on('init', () => {
        setEditorCommandState('DefaultParagraphSeparator', getForcedRootBlock(editor))
      })
    }
    const isAllContentSelected = (editor) => {
      const body = editor.getBody()
      const rng = editor.selection.getRng()
      return (
        rng.startContainer === rng.endContainer &&
        rng.startContainer === body &&
        rng.startOffset === 0 &&
        rng.endOffset === body.childNodes.length
      )
    }
    const normalizeSelection = () => {
      editor.on(
        'keyup focusin mouseup',
        (e) => {
          if (!VK.modifierPressed(e) && !isAllContentSelected(editor)) {
            selection.normalize()
          }
        },
        true
      )
    }
    const showBrokenImageIcon = () => {
      editor.contentStyles.push(
        'img:-moz-broken {' + '-moz-force-broken-image-icon:1;' + 'min-width:24px;' + 'min-height:24px' + '}'
      )
    }
    const restoreFocusOnKeyDown = () => {
      if (!editor.inline) {
        editor.on('keydown', () => {
          if (document.activeElement === document.body) {
            editor.getWin().focus()
          }
        })
      }
    }
    const bodyHeight = () => {
      if (!editor.inline) {
        editor.contentStyles.push('body {min-height: 150px}')
        editor.on('click', (e) => {
          let rng
          if (e.target.nodeName === 'HTML') {
            rng = editor.selection.getRng()
            editor.getBody().focus()
            editor.selection.setRng(rng)
            editor.selection.normalize()
            editor.nodeChanged()
          }
        })
      }
    }
    const blockCmdArrowNavigation = () => {
      if (isMac) {
        editor.on('keydown', (e) => {
          if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode === 37 || e.keyCode === 39)) {
            e.preventDefault()
            const selection = editor.selection.getSel()
            selection.modify('move', e.keyCode === 37 ? 'backward' : 'forward', 'lineboundary')
          }
        })
      }
    }
    const tapLinksAndImages = () => {
      editor.on('click', (e) => {
        let elm = e.target
        do {
          if (elm.tagName === 'A') {
            e.preventDefault()
            return
          }
        } while ((elm = elm.parentNode))
      })
      editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}')
    }
    const blockFormSubmitInsideEditor = () => {
      editor.on('init', () => {
        editor.dom.bind(editor.getBody(), 'submit', (e) => {
          e.preventDefault()
        })
      })
    }
    const removeAppleInterchangeBrs = () => {
      parser.addNodeFilter('br', (nodes) => {
        let i = nodes.length
        while (i--) {
          if (nodes[i].attr('class') === 'Apple-interchange-newline') {
            nodes[i].remove()
          }
        }
      })
    }
    const refreshContentEditable = noop
    const isHidden = () => {
      if (!isGecko || editor.removed) {
        return false
      }
      const sel = editor.selection.getSel()
      return !sel || !sel.rangeCount || sel.rangeCount === 0
    }
    const setupRtc = () => {
      if (isWebKit) {
        documentElementEditingFocus()
        selectControlElements()
        blockFormSubmitInsideEditor()
        selectAll()
        if (isiOS) {
          restoreFocusOnKeyDown()
          bodyHeight()
          tapLinksAndImages()
        }
      }
      if (isGecko) {
        focusBody()
        setGeckoEditingOptions()
        showBrokenImageIcon()
        blockCmdArrowNavigation()
      }
    }
    const setup = () => {
      removeBlockQuoteOnBackSpace()
      emptyEditorWhenDeleting()
      if (!Env.windowsPhone) {
        normalizeSelection()
      }
      if (isWebKit) {
        documentElementEditingFocus()
        selectControlElements()
        setDefaultBlockType()
        blockFormSubmitInsideEditor()
        disableBackspaceIntoATable()
        removeAppleInterchangeBrs()
        if (isiOS) {
          restoreFocusOnKeyDown()
          bodyHeight()
          tapLinksAndImages()
        } else {
          selectAll()
        }
      }
      if (isGecko) {
        removeHrOnBackspace()
        focusBody()
        removeStylesWhenDeletingAcrossBlockElements()
        setGeckoEditingOptions()
        addBrAfterLastLinks()
        showBrokenImageIcon()
        blockCmdArrowNavigation()
        disableBackspaceIntoATable()
      }
    }
    if (isRtc(editor)) {
      setupRtc()
    } else {
      setup()
    }
    return {
      refreshContentEditable,
      isHidden,
    }
  }

  const DOM$6 = DOMUtils.DOM
  const appendStyle = (editor, text) => {
    const body = SugarElement.fromDom(editor.getBody())
    const container = getStyleContainer(getRootNode(body))
    const style = SugarElement.fromTag('style')
    set$2(style, 'type', 'text/css')
    append$1(style, SugarElement.fromText(text))
    append$1(container, style)
    editor.on('remove', () => {
      remove$6(style)
    })
  }
  const getRootName = (editor) => (editor.inline ? editor.getElement().nodeName.toLowerCase() : undefined)
  const removeUndefined = (obj) => filter$5(obj, (v) => isUndefined(v) === false)
  const mkParserSettings = (editor) => {
    const getOption = editor.options.get
    const blobCache = editor.editorUpload.blobCache
    return removeUndefined({
      allow_conditional_comments: getOption('allow_conditional_comments'),
      allow_html_data_urls: getOption('allow_html_data_urls'),
      allow_svg_data_urls: getOption('allow_svg_data_urls'),
      allow_html_in_named_anchor: getOption('allow_html_in_named_anchor'),
      allow_script_urls: getOption('allow_script_urls'),
      allow_unsafe_link_target: getOption('allow_unsafe_link_target'),
      convert_fonts_to_spans: getOption('convert_fonts_to_spans'),
      fix_list_elements: getOption('fix_list_elements'),
      font_size_legacy_values: getOption('font_size_legacy_values'),
      forced_root_block: getOption('forced_root_block'),
      forced_root_block_attrs: getOption('forced_root_block_attrs'),
      preserve_cdata: getOption('preserve_cdata'),
      remove_trailing_brs: getOption('remove_trailing_brs'),
      inline_styles: getOption('inline_styles'),
      root_name: getRootName(editor),
      validate: true,
      blob_cache: blobCache,
      document: editor.getDoc(),
    })
  }
  const mkSchemaSettings = (editor) => {
    const getOption = editor.options.get
    return removeUndefined({
      custom_elements: getOption('custom_elements'),
      extended_valid_elements: getOption('extended_valid_elements'),
      invalid_elements: getOption('invalid_elements'),
      invalid_styles: getOption('invalid_styles'),
      schema: getOption('schema'),
      valid_children: getOption('valid_children'),
      valid_classes: getOption('valid_classes'),
      valid_elements: getOption('valid_elements'),
      valid_styles: getOption('valid_styles'),
      verify_html: getOption('verify_html'),
      padd_empty_block_inline_children: getOption('format_empty_lines'),
    })
  }
  const mkSerializerSettings = (editor) => {
    const getOption = editor.options.get
    return {
      ...mkParserSettings(editor),
      ...mkSchemaSettings(editor),
      ...removeUndefined({
        url_converter: getOption('url_converter'),
        url_converter_scope: getOption('url_converter_scope'),
        element_format: getOption('element_format'),
        entities: getOption('entities'),
        entity_encoding: getOption('entity_encoding'),
        indent: getOption('indent'),
        indent_after: getOption('indent_after'),
        indent_before: getOption('indent_before'),
      }),
    }
  }
  const createParser = (editor) => {
    const parser = DomParser(mkParserSettings(editor), editor.schema)
    parser.addAttributeFilter('src,href,style,tabindex', (nodes, name) => {
      let i = nodes.length,
        node,
        value
      const dom = editor.dom
      const internalName = 'data-mce-' + name
      while (i--) {
        node = nodes[i]
        value = node.attr(name)
        if (value && !node.attr(internalName)) {
          if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) {
            continue
          }
          if (name === 'style') {
            value = dom.serializeStyle(dom.parseStyle(value), node.name)
            if (!value.length) {
              value = null
            }
            node.attr(internalName, value)
            node.attr(name, value)
          } else if (name === 'tabindex') {
            node.attr(internalName, value)
            node.attr(name, null)
          } else {
            node.attr(internalName, editor.convertURL(value, name, node.name))
          }
        }
      }
    })
    parser.addNodeFilter('script', (nodes) => {
      let i = nodes.length
      while (i--) {
        const node = nodes[i]
        const type = node.attr('type') || 'no/type'
        if (type.indexOf('mce-') !== 0) {
          node.attr('type', 'mce-' + type)
        }
      }
    })
    if (editor.options.get('preserve_cdata')) {
      parser.addNodeFilter('#cdata', (nodes) => {
        let i = nodes.length
        while (i--) {
          const node = nodes[i]
          node.type = 8
          node.name = '#comment'
          node.value = '[CDATA[' + editor.dom.encode(node.value) + ']]'
        }
      })
    }
    parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', (nodes) => {
      let i = nodes.length
      const nonEmptyElements = editor.schema.getNonEmptyElements()
      while (i--) {
        const node = nodes[i]
        if (node.isEmpty(nonEmptyElements) && node.getAll('br').length === 0) {
          node.append(new AstNode('br', 1))
        }
      }
    })
    return parser
  }
  const autoFocus = (editor) => {
    const autoFocus = getAutoFocus(editor)
    if (autoFocus) {
      Delay.setEditorTimeout(
        editor,
        () => {
          let focusEditor
          if (autoFocus === true) {
            focusEditor = editor
          } else {
            focusEditor = editor.editorManager.get(autoFocus)
          }
          if (!focusEditor.destroyed) {
            focusEditor.focus()
          }
        },
        100
      )
    }
  }
  const moveSelectionToFirstCaretPosition = (editor) => {
    const root = editor.dom.getRoot()
    if (!editor.inline && (!hasAnyRanges(editor) || editor.selection.getStart(true) === root)) {
      firstPositionIn(root).each((pos) => {
        const node = pos.getNode()
        const caretPos = isTable$3(node) ? firstPositionIn(node).getOr(pos) : pos
        editor.selection.setRng(caretPos.toRange())
      })
    }
  }
  const initEditor = (editor) => {
    editor.bindPendingEventDelegates()
    editor.initialized = true
    fireInit(editor)
    editor.focus(true)
    moveSelectionToFirstCaretPosition(editor)
    editor.nodeChanged({ initial: true })
    const initInstanceCallback = getInitInstanceCallback(editor)
    if (isFunction(initInstanceCallback)) {
      initInstanceCallback.call(editor, editor)
    }
    autoFocus(editor)
  }
  const getStyleSheetLoader$1 = (editor) => (editor.inline ? editor.ui.styleSheetLoader : editor.dom.styleSheetLoader)
  const makeStylesheetLoadingPromises = (editor, css, framedFonts) => {
    const promises = [getStyleSheetLoader$1(editor).loadAll(css)]
    if (editor.inline) {
      return promises
    } else {
      return promises.concat([editor.ui.styleSheetLoader.loadAll(framedFonts)])
    }
  }
  const loadContentCss = (editor) => {
    const styleSheetLoader = getStyleSheetLoader$1(editor)
    const fontCss = getFontCss(editor)
    const css = editor.contentCSS
    const removeCss = () => {
      styleSheetLoader.unloadAll(css)
      if (!editor.inline) {
        editor.ui.styleSheetLoader.unloadAll(fontCss)
      }
    }
    const loaded = () => {
      if (editor.removed) {
        removeCss()
      } else {
        editor.on('remove', removeCss)
      }
    }
    if (editor.contentStyles.length > 0) {
      let contentCssText = ''
      Tools.each(editor.contentStyles, (style) => {
        contentCssText += style + '\r\n'
      })
      editor.dom.addStyle(contentCssText)
    }
    const allStylesheets = Promise.all(makeStylesheetLoadingPromises(editor, css, fontCss)).then(loaded).catch(loaded)
    const contentStyle = getContentStyle(editor)
    if (contentStyle) {
      appendStyle(editor, contentStyle)
    }
    return allStylesheets
  }
  const preInit = (editor) => {
    const doc = editor.getDoc(),
      body = editor.getBody()
    firePreInit(editor)
    if (!shouldBrowserSpellcheck(editor)) {
      doc.body.spellcheck = false
      DOM$6.setAttrib(body, 'spellcheck', 'false')
    }
    editor.quirks = Quirks(editor)
    firePostRender(editor)
    const directionality = getDirectionality(editor)
    if (directionality !== undefined) {
      body.dir = directionality
    }
    const protect = getProtect(editor)
    if (protect) {
      editor.on('BeforeSetContent', (e) => {
        Tools.each(protect, (pattern) => {
          e.content = e.content.replace(pattern, (str) => {
            return '<!--mce:protected ' + escape(str) + '-->'
          })
        })
      })
    }
    editor.on('SetContent', () => {
      editor.addVisual(editor.getBody())
    })
    editor.on('compositionstart compositionend', (e) => {
      editor.composing = e.type === 'compositionstart'
    })
  }
  const loadInitialContent = (editor) => {
    if (!isRtc(editor)) {
      editor.load({
        initial: true,
        format: 'html',
      })
    }
    editor.startContent = editor.getContent({ format: 'raw' })
  }
  const initEditorWithInitialContent = (editor) => {
    if (editor.removed !== true) {
      loadInitialContent(editor)
      initEditor(editor)
    }
  }
  const contentBodyLoaded = (editor) => {
    const targetElm = editor.getElement()
    let doc = editor.getDoc()
    if (editor.inline) {
      DOM$6.addClass(targetElm, 'mce-content-body')
      editor.contentDocument = doc = document
      editor.contentWindow = window
      editor.bodyElement = targetElm
      editor.contentAreaContainer = targetElm
    }
    const body = editor.getBody()
    body.disabled = true
    editor.readonly = isReadOnly$1(editor)
    if (!editor.readonly) {
      if (editor.inline && DOM$6.getStyle(body, 'position', true) === 'static') {
        body.style.position = 'relative'
      }
      body.contentEditable = 'true'
    }
    body.disabled = false
    editor.editorUpload = EditorUpload(editor)
    editor.schema = Schema(mkSchemaSettings(editor))
    editor.dom = DOMUtils(doc, {
      keep_values: true,
      url_converter: editor.convertURL,
      url_converter_scope: editor,
      update_styles: true,
      root_element: editor.inline ? editor.getBody() : null,
      collect: () => editor.inline,
      schema: editor.schema,
      contentCssCors: shouldUseContentCssCors(editor),
      referrerPolicy: getReferrerPolicy(editor),
      onSetAttrib: (e) => {
        editor.dispatch('SetAttrib', e)
      },
    })
    editor.parser = createParser(editor)
    editor.serializer = DomSerializer(mkSerializerSettings(editor), editor)
    editor.selection = EditorSelection(editor.dom, editor.getWin(), editor.serializer, editor)
    editor.annotator = Annotator(editor)
    editor.formatter = Formatter(editor)
    editor.undoManager = UndoManager(editor)
    editor._nodeChangeDispatcher = new NodeChange(editor)
    editor._selectionOverrides = SelectionOverrides(editor)
    setup$o(editor)
    setup$6(editor)
    setup$m(editor)
    if (!isRtc(editor)) {
      setup$5(editor)
      setup$1(editor)
    }
    const caret = setup$b(editor)
    setup$p(editor, caret)
    setup$n(editor)
    setup$q(editor)
    setup$7(editor)
    const setupRtcThunk = setup$s(editor)
    preInit(editor)
    setupRtcThunk.fold(
      () => {
        loadContentCss(editor).then(() => initEditorWithInitialContent(editor))
      },
      (setupRtc) => {
        editor.setProgressState(true)
        loadContentCss(editor).then(() => {
          setupRtc().then(
            (_rtcMode) => {
              editor.setProgressState(false)
              initEditorWithInitialContent(editor)
              bindEvents(editor)
            },
            (err) => {
              editor.notificationManager.open({
                type: 'error',
                text: String(err),
              })
              initEditorWithInitialContent(editor)
              bindEvents(editor)
            }
          )
        })
      }
    )
  }
  const initContentBody = (editor, skipWrite) => {
    if (!editor.inline) {
      editor.getElement().style.visibility = editor.orgVisibility
    }
    if (!skipWrite && !editor.inline) {
      const iframe = editor.iframeElement
      const binder = bind$1(SugarElement.fromDom(iframe), 'load', () => {
        binder.unbind()
        editor.contentDocument = iframe.contentDocument
        contentBodyLoaded(editor)
      })
      if (Env.browser.isFirefox()) {
        const doc = editor.getDoc()
        doc.open()
        doc.write(editor.iframeHTML)
        doc.close()
      } else {
        iframe.srcdoc = editor.iframeHTML
      }
    } else {
      contentBodyLoaded(editor)
    }
  }

  const DOM$5 = DOMUtils.DOM
  const createIframeElement = (id, title, customAttrs, tabindex) => {
    const iframe = SugarElement.fromTag('iframe')
    tabindex.each((t) => set$2(iframe, 'tabindex', t))
    setAll$1(iframe, customAttrs)
    setAll$1(iframe, {
      id: id + '_ifr',
      frameBorder: '0',
      allowTransparency: 'true',
      title,
    })
    add$2(iframe, 'tox-edit-area__iframe')
    return iframe
  }
  const getIframeHtml = (editor) => {
    let iframeHTML = getDocType(editor) + '<html><head>'
    if (getDocumentBaseUrl(editor) !== editor.documentBaseUrl) {
      iframeHTML += '<base href="' + editor.documentBaseURI.getURI() + '" />'
    }
    iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'
    const bodyId = getBodyId(editor)
    const bodyClass = getBodyClass(editor)
    const translatedAriaText = editor.translate(getIframeAriaText(editor))
    if (getContentSecurityPolicy(editor)) {
      iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + getContentSecurityPolicy(editor) + '" />'
    }
    iframeHTML +=
      '</head>' +
      `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" aria-label="${translatedAriaText}">` +
      '<br>' +
      '</body></html>'
    return iframeHTML
  }
  const createIframe = (editor, boxInfo) => {
    const iframeTitle = editor.translate('Rich Text Area')
    const tabindex = getOpt(SugarElement.fromDom(editor.getElement()), 'tabindex').bind(toInt)
    const ifr = createIframeElement(editor.id, iframeTitle, getIframeAttrs(editor), tabindex).dom
    ifr.onload = () => {
      ifr.onload = null
      editor.dispatch('load')
    }
    editor.contentAreaContainer = boxInfo.iframeContainer
    editor.iframeElement = ifr
    editor.iframeHTML = getIframeHtml(editor)
    DOM$5.add(boxInfo.iframeContainer, ifr)
  }
  const init$1 = (editor, boxInfo) => {
    createIframe(editor, boxInfo)
    if (boxInfo.editorContainer) {
      DOM$5.get(boxInfo.editorContainer).style.display = editor.orgDisplay
      editor.hidden = DOM$5.isHidden(boxInfo.editorContainer)
    }
    editor.getElement().style.display = 'none'
    DOM$5.setAttrib(editor.id, 'aria-hidden', 'true')
    initContentBody(editor)
  }

  const DOM$4 = DOMUtils.DOM
  const initPlugin = (editor, initializedPlugins, plugin) => {
    const Plugin = PluginManager.get(plugin)
    const pluginUrl = PluginManager.urls[plugin] || editor.documentBaseUrl.replace(/\/$/, '')
    plugin = Tools.trim(plugin)
    if (Plugin && Tools.inArray(initializedPlugins, plugin) === -1) {
      if (editor.plugins[plugin]) {
        return
      }
      try {
        const pluginInstance = Plugin(editor, pluginUrl) || {}
        editor.plugins[plugin] = pluginInstance
        if (isFunction(pluginInstance.init)) {
          pluginInstance.init(editor, pluginUrl)
          initializedPlugins.push(plugin)
        }
      } catch (e) {
        pluginInitError(editor, plugin, e)
      }
    }
  }
  const trimLegacyPrefix = (name) => {
    return name.replace(/^\-/, '')
  }
  const initPlugins = (editor) => {
    const initializedPlugins = []
    each$f(getPlugins(editor), (name) => {
      initPlugin(editor, initializedPlugins, trimLegacyPrefix(name))
    })
  }
  const initIcons = (editor) => {
    const iconPackName = Tools.trim(getIconPackName(editor))
    const currentIcons = editor.ui.registry.getAll().icons
    const loadIcons = {
      ...IconManager.get('default').icons,
      ...IconManager.get(iconPackName).icons,
    }
    each$e(loadIcons, (svgData, icon) => {
      if (!has$2(currentIcons, icon)) {
        editor.ui.registry.addIcon(icon, svgData)
      }
    })
  }
  const initTheme = (editor) => {
    const theme = getTheme(editor)
    if (isString(theme)) {
      const Theme = ThemeManager.get(theme)
      editor.theme = Theme(editor, ThemeManager.urls[theme]) || {}
      if (isFunction(editor.theme.init)) {
        editor.theme.init(editor, ThemeManager.urls[theme] || editor.documentBaseUrl.replace(/\/$/, ''))
      }
    } else {
      editor.theme = {}
    }
  }
  const initModel = (editor) => {
    const model = getModel(editor)
    const Model = ModelManager.get(model)
    editor.model = Model(editor, ModelManager.urls[model])
  }
  const renderFromLoadedTheme = (editor) => {
    return editor.theme.renderUI()
  }
  const renderFromThemeFunc = (editor) => {
    const elm = editor.getElement()
    const theme = getTheme(editor)
    const info = theme(editor, elm)
    if (info.editorContainer.nodeType) {
      info.editorContainer.id = info.editorContainer.id || editor.id + '_parent'
    }
    if (info.iframeContainer && info.iframeContainer.nodeType) {
      info.iframeContainer.id = info.iframeContainer.id || editor.id + '_iframecontainer'
    }
    info.height = info.iframeHeight ? info.iframeHeight : elm.offsetHeight
    return info
  }
  const createThemeFalseResult = (element) => {
    return {
      editorContainer: element,
      iframeContainer: element,
      api: {},
    }
  }
  const renderThemeFalseIframe = (targetElement) => {
    const iframeContainer = DOM$4.create('div')
    DOM$4.insertAfter(iframeContainer, targetElement)
    return createThemeFalseResult(iframeContainer)
  }
  const renderThemeFalse = (editor) => {
    const targetElement = editor.getElement()
    return editor.inline ? createThemeFalseResult(null) : renderThemeFalseIframe(targetElement)
  }
  const renderThemeUi = (editor) => {
    const elm = editor.getElement()
    editor.orgDisplay = elm.style.display
    if (isString(getTheme(editor))) {
      return renderFromLoadedTheme(editor)
    } else if (isFunction(getTheme(editor))) {
      return renderFromThemeFunc(editor)
    } else {
      return renderThemeFalse(editor)
    }
  }
  const augmentEditorUiApi = (editor, api) => {
    const uiApiFacade = {
      show: Optional.from(api.show).getOr(noop),
      hide: Optional.from(api.hide).getOr(noop),
      isEnabled: Optional.from(api.isEnabled).getOr(always),
      setEnabled: (state) => {
        if (!editor.mode.isReadOnly()) {
          Optional.from(api.setEnabled).each((f) => f(state))
        }
      },
    }
    editor.ui = {
      ...editor.ui,
      ...uiApiFacade,
    }
  }
  const init = (editor) => {
    editor.dispatch('ScriptsLoaded')
    initIcons(editor)
    initTheme(editor)
    initModel(editor)
    initPlugins(editor)
    const renderInfo = renderThemeUi(editor)
    augmentEditorUiApi(editor, Optional.from(renderInfo.api).getOr({}))
    const boxInfo = {
      editorContainer: renderInfo.editorContainer,
      iframeContainer: renderInfo.iframeContainer,
    }
    editor.editorContainer = boxInfo.editorContainer ? boxInfo.editorContainer : null
    appendContentCssFromSettings(editor)
    if (editor.inline) {
      return initContentBody(editor)
    } else {
      return init$1(editor, boxInfo)
    }
  }

  const DOM$3 = DOMUtils.DOM
  const hasSkipLoadPrefix = (name) => name.charAt(0) === '-'
  const loadLanguage = (scriptLoader, editor) => {
    const languageCode = getLanguageCode(editor)
    const languageUrl = getLanguageUrl(editor)
    if (I18n.hasCode(languageCode) === false && languageCode !== 'en') {
      const url = isNotEmpty(languageUrl) ? languageUrl : `${editor.editorManager.baseURL}/langs/${languageCode}.js`
      scriptLoader.add(url).catch(() => {
        languageLoadError(editor, url, languageCode)
      })
    }
  }
  const loadTheme = (editor, suffix) => {
    const theme = getTheme(editor)
    if (isString(theme) && !hasSkipLoadPrefix(theme) && !has$2(ThemeManager.urls, theme)) {
      const themeUrl = getThemeUrl(editor)
      const url = themeUrl ? editor.documentBaseURI.toAbsolute(themeUrl) : `themes/${theme}/theme${suffix}.js`
      ThemeManager.load(theme, url).catch(() => {
        themeLoadError(editor, url, theme)
      })
    }
  }
  const loadModel = (editor, suffix) => {
    const model = getModel(editor)
    if (model !== 'plugin' && !has$2(ModelManager.urls, model)) {
      const modelUrl = getModelUrl(editor)
      const url = isString(modelUrl) ? editor.documentBaseURI.toAbsolute(modelUrl) : `models/${model}/model${suffix}.js`
      ModelManager.load(model, url).catch(() => {
        modelLoadError(editor, url, model)
      })
    }
  }
  const getIconsUrlMetaFromUrl = (editor) =>
    Optional.from(getIconsUrl(editor))
      .filter(isNotEmpty)
      .map((url) => ({
        url,
        name: Optional.none(),
      }))
  const getIconsUrlMetaFromName = (editor, name, suffix) =>
    Optional.from(name)
      .filter((name) => isNotEmpty(name) && !IconManager.has(name))
      .map((name) => ({
        url: `${editor.editorManager.baseURL}/icons/${name}/icons${suffix}.js`,
        name: Optional.some(name),
      }))
  const loadIcons = (scriptLoader, editor, suffix) => {
    const defaultIconsUrl = getIconsUrlMetaFromName(editor, 'default', suffix)
    const customIconsUrl = getIconsUrlMetaFromUrl(editor).orThunk(() =>
      getIconsUrlMetaFromName(editor, getIconPackName(editor), '')
    )
    each$f(cat([defaultIconsUrl, customIconsUrl]), (urlMeta) => {
      scriptLoader.add(urlMeta.url).catch(() => {
        iconsLoadError(editor, urlMeta.url, urlMeta.name.getOrUndefined())
      })
    })
  }
  const loadPlugins = (editor, suffix) => {
    const loadPlugin = (name, url) => {
      PluginManager.load(name, url).catch(() => {
        pluginLoadError(editor, url, name)
      })
    }
    each$e(getExternalPlugins$1(editor), (url, name) => {
      loadPlugin(name, url)
      editor.options.set('plugins', getPlugins(editor).concat(name))
    })
    each$f(getPlugins(editor), (plugin) => {
      plugin = Tools.trim(plugin)
      if (plugin && !PluginManager.urls[plugin] && !hasSkipLoadPrefix(plugin)) {
        loadPlugin(plugin, `plugins/${plugin}/plugin${suffix}.js`)
      }
    })
  }
  const isThemeLoaded = (editor) => {
    const theme = getTheme(editor)
    return !isString(theme) || isNonNullable(ThemeManager.get(theme))
  }
  const isModelLoaded = (editor) => {
    const model = getModel(editor)
    return isNonNullable(ModelManager.get(model))
  }
  const loadScripts = (editor, suffix) => {
    const scriptLoader = ScriptLoader.ScriptLoader
    const initEditor = () => {
      if (!editor.removed && isThemeLoaded(editor) && isModelLoaded(editor)) {
        init(editor)
      }
    }
    loadTheme(editor, suffix)
    loadModel(editor, suffix)
    loadLanguage(scriptLoader, editor)
    loadIcons(scriptLoader, editor, suffix)
    loadPlugins(editor, suffix)
    scriptLoader.loadQueue().then(initEditor, initEditor)
  }
  const getStyleSheetLoader = (element, editor) =>
    instance.forElement(element, {
      contentCssCors: hasContentCssCors(editor),
      referrerPolicy: getReferrerPolicy(editor),
    })
  const render = (editor) => {
    const id = editor.id
    I18n.setCode(getLanguageCode(editor))
    const readyHandler = () => {
      DOM$3.unbind(window, 'ready', readyHandler)
      editor.render()
    }
    if (!EventUtils.Event.domLoaded) {
      DOM$3.bind(window, 'ready', readyHandler)
      return
    }
    if (!editor.getElement()) {
      return
    }
    const element = SugarElement.fromDom(editor.getElement())
    const snapshot = clone$4(element)
    editor.on('remove', () => {
      eachr(element.dom.attributes, (attr) => remove$b(element, attr.name))
      setAll$1(element, snapshot)
    })
    editor.ui.styleSheetLoader = getStyleSheetLoader(element, editor)
    if (!isInline(editor)) {
      editor.orgVisibility = editor.getElement().style.visibility
      editor.getElement().style.visibility = 'hidden'
    } else {
      editor.inline = true
    }
    const form = editor.getElement().form || DOM$3.getParent(id, 'form')
    if (form) {
      editor.formElement = form
      if (hasHiddenInput(editor) && !isTextareaOrInput(editor.getElement())) {
        DOM$3.insertAfter(
          DOM$3.create('input', {
            type: 'hidden',
            name: id,
          }),
          id
        )
        editor.hasHiddenInput = true
      }
      editor.formEventDelegate = (e) => {
        editor.dispatch(e.type, e)
      }
      DOM$3.bind(form, 'submit reset', editor.formEventDelegate)
      editor.on('reset', () => {
        editor.resetContent()
      })
      if (shouldPatchSubmit(editor) && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
        form._mceOldSubmit = form.submit
        form.submit = () => {
          editor.editorManager.triggerSave()
          editor.setDirty(false)
          return form._mceOldSubmit(form)
        }
      }
    }
    editor.windowManager = WindowManager(editor)
    editor.notificationManager = NotificationManager(editor)
    if (isEncodingXml(editor)) {
      editor.on('GetContent', (e) => {
        if (e.save) {
          e.content = DOM$3.encode(e.content)
        }
      })
    }
    if (shouldAddFormSubmitTrigger(editor)) {
      editor.on('submit', () => {
        if (editor.initialized) {
          editor.save()
        }
      })
    }
    if (shouldAddUnloadTrigger(editor)) {
      editor._beforeUnload = () => {
        if (editor.initialized && !editor.destroyed && !editor.isHidden()) {
          editor.save({
            format: 'raw',
            no_events: true,
            set_dirty: false,
          })
        }
      }
      editor.editorManager.on('BeforeUnload', editor._beforeUnload)
    }
    editor.editorManager.add(editor)
    loadScripts(editor, editor.suffix)
  }

  const sectionResult = (sections, settings) => ({
    sections: constant(sections),
    options: constant(settings),
  })
  const deviceDetection = detect$2().deviceType
  const isPhone = deviceDetection.isPhone()
  const isTablet = deviceDetection.isTablet()
  const normalizePlugins = (plugins) => {
    if (isNullable(plugins)) {
      return []
    } else {
      const pluginNames = isArray$1(plugins) ? plugins : plugins.split(/[ ,]/)
      const trimmedPlugins = map$3(pluginNames, trim$3)
      return filter$6(trimmedPlugins, isNotEmpty)
    }
  }
  const extractSections = (keys, options) => {
    const result = bifilter(options, (value, key) => {
      return contains$2(keys, key)
    })
    return sectionResult(result.t, result.f)
  }
  const getSection = (sectionResult, name, defaults = {}) => {
    const sections = sectionResult.sections()
    const sectionOptions = get$a(sections, name).getOr({})
    return Tools.extend({}, defaults, sectionOptions)
  }
  const hasSection = (sectionResult, name) => {
    return has$2(sectionResult.sections(), name)
  }
  const getSectionConfig = (sectionResult, name) => {
    return hasSection(sectionResult, name) ? sectionResult.sections()[name] : {}
  }
  const getMobileOverrideOptions = (mobileOptions, isPhone) => {
    const defaultMobileOptions = {
      table_grid: false,
      object_resizing: false,
      resize: false,
      toolbar_mode: get$a(mobileOptions, 'toolbar_mode').getOr('scrolling'),
      toolbar_sticky: false,
    }
    const defaultPhoneOptions = { menubar: false }
    return {
      ...defaultMobileOptions,
      ...(isPhone ? defaultPhoneOptions : {}),
    }
  }
  const getExternalPlugins = (overrideOptions, options) => {
    var _a
    const userDefinedExternalPlugins = (_a = options.external_plugins) !== null && _a !== void 0 ? _a : {}
    if (overrideOptions && overrideOptions.external_plugins) {
      return Tools.extend({}, overrideOptions.external_plugins, userDefinedExternalPlugins)
    } else {
      return userDefinedExternalPlugins
    }
  }
  const combinePlugins = (forcedPlugins, plugins) => {
    return [].concat(normalizePlugins(forcedPlugins)).concat(normalizePlugins(plugins))
  }
  const getPlatformPlugins = (isMobileDevice, sectionResult, desktopPlugins, mobilePlugins) => {
    if (isMobileDevice && hasSection(sectionResult, 'mobile')) {
      return mobilePlugins
    } else {
      return desktopPlugins
    }
  }
  const processPlugins = (isMobileDevice, sectionResult, defaultOverrideOptions, options) => {
    const forcedPlugins = normalizePlugins(defaultOverrideOptions.forced_plugins)
    const desktopPlugins = normalizePlugins(options.plugins)
    const mobileConfig = getSectionConfig(sectionResult, 'mobile')
    const mobilePlugins = mobileConfig.plugins ? normalizePlugins(mobileConfig.plugins) : desktopPlugins
    const platformPlugins = getPlatformPlugins(isMobileDevice, sectionResult, desktopPlugins, mobilePlugins)
    const combinedPlugins = combinePlugins(forcedPlugins, platformPlugins)
    return Tools.extend(options, {
      forced_plugins: forcedPlugins,
      plugins: combinedPlugins,
    })
  }
  const isOnMobile = (isMobileDevice, sectionResult) => {
    return isMobileDevice && hasSection(sectionResult, 'mobile')
  }
  const combineOptions = (isMobileDevice, isPhone, defaultOptions, defaultOverrideOptions, options) => {
    var _a
    const deviceOverrideOptions = isMobileDevice
      ? { mobile: getMobileOverrideOptions((_a = options.mobile) !== null && _a !== void 0 ? _a : {}, isPhone) }
      : {}
    const sectionResult = extractSections(['mobile'], deepMerge(deviceOverrideOptions, options))
    const extendedOptions = Tools.extend(
      defaultOptions,
      defaultOverrideOptions,
      sectionResult.options(),
      isOnMobile(isMobileDevice, sectionResult) ? getSection(sectionResult, 'mobile') : {},
      { external_plugins: getExternalPlugins(defaultOverrideOptions, sectionResult.options()) }
    )
    return processPlugins(isMobileDevice, sectionResult, defaultOverrideOptions, extendedOptions)
  }
  const normalizeOptions = (defaultOverrideOptions, options) =>
    combineOptions(isPhone || isTablet, isPhone, options, defaultOverrideOptions, options)

  const addVisual = (editor, elm) => addVisual$1(editor, elm)

  const registerExecCommands$3 = (editor) => {
    const toggleFormat = (name, value) => {
      editor.formatter.toggle(name, value)
      editor.nodeChanged()
    }
    const toggleAlign = (align) => () => {
      each$f('left,center,right,justify'.split(','), (name) => {
        if (align !== name) {
          editor.formatter.remove('align' + name)
        }
      })
      if (align !== 'none') {
        toggleFormat('align' + align)
      }
    }
    editor.editorCommands.addCommands({
      JustifyLeft: toggleAlign('left'),
      JustifyCenter: toggleAlign('center'),
      JustifyRight: toggleAlign('right'),
      JustifyFull: toggleAlign('justify'),
      JustifyNone: toggleAlign('none'),
    })
  }
  const registerQueryStateCommands$1 = (editor) => {
    const alignStates = (name) => () => {
      const selection = editor.selection
      const nodes = selection.isCollapsed()
        ? [editor.dom.getParent(selection.getNode(), editor.dom.isBlock)]
        : selection.getSelectedBlocks()
      return exists(nodes, (node) => isNonNullable(editor.formatter.matchNode(node, name)))
    }
    editor.editorCommands.addCommands(
      {
        JustifyLeft: alignStates('alignleft'),
        JustifyCenter: alignStates('aligncenter'),
        JustifyRight: alignStates('alignright'),
        JustifyFull: alignStates('alignjustify'),
      },
      'state'
    )
  }
  const registerCommands$a = (editor) => {
    registerExecCommands$3(editor)
    registerQueryStateCommands$1(editor)
  }

  const registerCommands$9 = (editor) => {
    editor.editorCommands.addCommands({
      'Cut,Copy,Paste': (command) => {
        const doc = editor.getDoc()
        let failed
        try {
          doc.execCommand(command)
        } catch (ex) {
          failed = true
        }
        if (command === 'paste' && !doc.queryCommandEnabled(command)) {
          failed = true
        }
        if (failed || !doc.queryCommandSupported(command)) {
          let msg = editor.translate(
            `Your browser doesn't support direct access to the clipboard. ` +
              'Please use the Ctrl+X/C/V keyboard shortcuts instead.'
          )
          if (Env.os.isMacOS() || Env.os.isiOS()) {
            msg = msg.replace(/Ctrl\+/g, '\u2318+')
          }
          editor.notificationManager.open({
            text: msg,
            type: 'error',
          })
        }
      },
    })
  }

  const trimOrPadLeftRight = (dom, rng, html) => {
    const root = SugarElement.fromDom(dom.getRoot())
    if (needsToBeNbspLeft(root, CaretPosition.fromRangeStart(rng))) {
      html = html.replace(/^ /, '&nbsp;')
    } else {
      html = html.replace(/^&nbsp;/, ' ')
    }
    if (needsToBeNbspRight(root, CaretPosition.fromRangeEnd(rng))) {
      html = html.replace(/(&nbsp;| )(<br( \/)>)?$/, '&nbsp;')
    } else {
      html = html.replace(/&nbsp;(<br( \/)?>)?$/, ' ')
    }
    return html
  }

  const processValue$1 = (value) => {
    if (typeof value !== 'string') {
      const details = Tools.extend(
        {
          paste: value.paste,
          data: { paste: value.paste },
        },
        value
      )
      return {
        content: value.content,
        details,
      }
    }
    return {
      content: value,
      details: {},
    }
  }
  const trimOrPad = (editor, value) => {
    const selection = editor.selection
    const dom = editor.dom
    if (/^ | $/.test(value)) {
      return trimOrPadLeftRight(dom, selection.getRng(), value)
    } else {
      return value
    }
  }
  const insertAtCaret = (editor, value) => {
    const { content, details } = processValue$1(value)
    preProcessSetContent(editor, {
      content: trimOrPad(editor, content),
      format: 'html',
      set: false,
      selection: true,
      paste: details.paste,
    }).each((args) => {
      const insertedContent = insertContent$1(editor, args.content, details)
      postProcessSetContent(editor, insertedContent, args)
      editor.addVisual()
    })
  }

  const registerCommands$8 = (editor) => {
    editor.editorCommands.addCommands({
      mceCleanup: () => {
        const bm = editor.selection.getBookmark()
        editor.setContent(editor.getContent())
        editor.selection.moveToBookmark(bm)
      },
      insertImage: (_command, _ui, value) => {
        insertAtCaret(editor, editor.dom.createHTML('img', { src: value }))
      },
      insertHorizontalRule: () => {
        editor.execCommand('mceInsertContent', false, '<hr>')
      },
      insertText: (_command, _ui, value) => {
        insertAtCaret(editor, editor.dom.encode(value))
      },
      insertHTML: (_command, _ui, value) => {
        insertAtCaret(editor, value)
      },
      mceInsertContent: (_command, _ui, value) => {
        insertAtCaret(editor, value)
      },
      mceSetContent: (_command, _ui, value) => {
        editor.setContent(value)
      },
      mceReplaceContent: (_command, _ui, value) => {
        editor.execCommand(
          'mceInsertContent',
          false,
          value.replace(/\{\$selection\}/g, editor.selection.getContent({ format: 'text' }))
        )
      },
      mceNewDocument: () => {
        editor.setContent('')
      },
    })
  }

  const legacyPropNames = {
    'font-size': 'size',
    'font-family': 'face',
  }
  const getSpecifiedFontProp = (propName, rootElm, elm) => {
    const getProperty = (elm) =>
      getRaw$1(elm, propName).orThunk(() => {
        if (name(elm) === 'font') {
          return get$a(legacyPropNames, propName).bind((legacyPropName) => getOpt(elm, legacyPropName))
        } else {
          return Optional.none()
        }
      })
    const isRoot = (elm) => eq(SugarElement.fromDom(rootElm), elm)
    return closest$2(SugarElement.fromDom(elm), (elm) => getProperty(elm), isRoot)
  }
  const normalizeFontFamily = (fontFamily) => fontFamily.replace(/[\'\"\\]/g, '').replace(/,\s+/g, ',')
  const getComputedFontProp = (propName, elm) => Optional.from(DOMUtils.DOM.getStyle(elm, propName, true))
  const getFontProp = (propName) => (rootElm, elm) =>
    Optional.from(elm)
      .map(SugarElement.fromDom)
      .filter(isElement$7)
      .bind((element) =>
        getSpecifiedFontProp(propName, rootElm, element.dom).or(getComputedFontProp(propName, element.dom))
      )
      .getOr('')
  const getFontSize = getFontProp('font-size')
  const getFontFamily = compose(normalizeFontFamily, getFontProp('font-family'))

  const findFirstCaretElement = (editor) =>
    firstPositionIn(editor.getBody()).map((caret) => {
      const container = caret.container()
      return isText$9(container) ? container.parentNode : container
    })
  const getCaretElement = (editor) =>
    Optional.from(editor.selection.getRng()).bind((rng) => {
      const root = editor.getBody()
      const atStartOfNode = rng.startContainer === root && rng.startOffset === 0
      return atStartOfNode ? Optional.none() : Optional.from(editor.selection.getStart(true))
    })
  const bindRange = (editor, binder) =>
    getCaretElement(editor)
      .orThunk(curry(findFirstCaretElement, editor))
      .map(SugarElement.fromDom)
      .filter(isElement$7)
      .bind(binder)
  const mapRange = (editor, mapper) => bindRange(editor, compose1(Optional.some, mapper))

  const fromFontSizeNumber = (editor, value) => {
    if (/^[0-9.]+$/.test(value)) {
      const fontSizeNumber = parseInt(value, 10)
      if (fontSizeNumber >= 1 && fontSizeNumber <= 7) {
        const fontSizes = getFontStyleValues(editor)
        const fontClasses = getFontSizeClasses(editor)
        if (fontClasses) {
          return fontClasses[fontSizeNumber - 1] || value
        } else {
          return fontSizes[fontSizeNumber - 1] || value
        }
      } else {
        return value
      }
    } else {
      return value
    }
  }
  const normalizeFontNames = (font) => {
    const fonts = font.split(/\s*,\s*/)
    return map$3(fonts, (font) => {
      if (font.indexOf(' ') !== -1 && !(startsWith(font, '"') || startsWith(font, `'`))) {
        return `'${font}'`
      } else {
        return font
      }
    }).join(',')
  }
  const fontNameAction = (editor, value) => {
    const font = fromFontSizeNumber(editor, value)
    editor.formatter.toggle('fontname', { value: normalizeFontNames(font) })
    editor.nodeChanged()
  }
  const fontNameQuery = (editor) => mapRange(editor, (elm) => getFontFamily(editor.getBody(), elm.dom)).getOr('')
  const fontSizeAction = (editor, value) => {
    editor.formatter.toggle('fontsize', { value: fromFontSizeNumber(editor, value) })
    editor.nodeChanged()
  }
  const fontSizeQuery = (editor) => mapRange(editor, (elm) => getFontSize(editor.getBody(), elm.dom)).getOr('')

  const lineHeightQuery = (editor) =>
    mapRange(editor, (elm) => {
      const root = SugarElement.fromDom(editor.getBody())
      const specifiedStyle = closest$2(elm, (elm) => getRaw$1(elm, 'line-height'), curry(eq, root))
      const computedStyle = () => {
        const lineHeight = parseFloat(get$7(elm, 'line-height'))
        const fontSize = parseFloat(get$7(elm, 'font-size'))
        return String(lineHeight / fontSize)
      }
      return specifiedStyle.getOrThunk(computedStyle)
    }).getOr('')
  const lineHeightAction = (editor, lineHeight) => {
    editor.formatter.toggle('lineheight', { value: String(lineHeight) })
    editor.nodeChanged()
  }

  const registerExecCommands$2 = (editor) => {
    const toggleFormat = (name, value) => {
      editor.formatter.toggle(name, value)
      editor.nodeChanged()
    }
    editor.editorCommands.addCommands({
      'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': (command) => {
        toggleFormat(command)
      },
      'ForeColor,HiliteColor': (command, _ui, value) => {
        toggleFormat(command, { value })
      },
      'BackColor': (_command, _ui, value) => {
        toggleFormat('hilitecolor', { value })
      },
      'FontName': (_command, _ui, value) => {
        fontNameAction(editor, value)
      },
      'FontSize': (_command, _ui, value) => {
        fontSizeAction(editor, value)
      },
      'LineHeight': (_command, _ui, value) => {
        lineHeightAction(editor, value)
      },
      'Lang': (command, _ui, lang) => {
        toggleFormat(command, {
          value: lang.code,
          customValue: lang.customCode,
        })
      },
      'RemoveFormat': (command) => {
        editor.formatter.remove(command)
      },
      'mceBlockQuote': () => {
        toggleFormat('blockquote')
      },
      'FormatBlock': (_command, _ui, value) => {
        toggleFormat(isString(value) ? value : 'p')
      },
      'mceToggleFormat': (_command, _ui, value) => {
        toggleFormat(value)
      },
    })
  }
  const registerQueryValueCommands = (editor) => {
    const isFormatMatch = (name) => editor.formatter.match(name)
    editor.editorCommands.addCommands(
      {
        'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': (command) => isFormatMatch(command),
        'mceBlockQuote': () => isFormatMatch('blockquote'),
      },
      'state'
    )
    editor.editorCommands.addQueryValueHandler('FontName', () => fontNameQuery(editor))
    editor.editorCommands.addQueryValueHandler('FontSize', () => fontSizeQuery(editor))
    editor.editorCommands.addQueryValueHandler('LineHeight', () => lineHeightQuery(editor))
  }
  const registerCommands$7 = (editor) => {
    registerExecCommands$2(editor)
    registerQueryValueCommands(editor)
  }

  const registerCommands$6 = (editor) => {
    editor.editorCommands.addCommands({
      mceAddUndoLevel: () => {
        editor.undoManager.add()
      },
      mceEndUndoLevel: () => {
        editor.undoManager.add()
      },
      Undo: () => {
        editor.undoManager.undo()
      },
      Redo: () => {
        editor.undoManager.redo()
      },
    })
  }

  const registerCommands$5 = (editor) => {
    editor.editorCommands.addCommands({
      Indent: () => {
        indent(editor)
      },
      Outdent: () => {
        outdent(editor)
      },
    })
    editor.editorCommands.addCommands({ Outdent: () => canOutdent(editor) }, 'state')
  }

  const registerCommands$4 = (editor) => {
    const applyLinkToSelection = (_command, _ui, value) => {
      const linkDetails = isString(value) ? { href: value } : value
      const anchor = editor.dom.getParent(editor.selection.getNode(), 'a')
      if (isObject(linkDetails) && isString(linkDetails.href)) {
        linkDetails.href = linkDetails.href.replace(/ /g, '%20')
        if (!anchor || !linkDetails.href) {
          editor.formatter.remove('link')
        }
        if (linkDetails.href) {
          editor.formatter.apply('link', linkDetails, anchor)
        }
      }
    }
    editor.editorCommands.addCommands({
      unlink: () => {
        if (editor.selection.isCollapsed()) {
          const elm = editor.dom.getParent(editor.selection.getStart(), 'a')
          if (elm) {
            editor.dom.remove(elm, true)
          }
          return
        }
        editor.formatter.remove('link')
      },
      mceInsertLink: applyLinkToSelection,
      createLink: applyLinkToSelection,
    })
  }

  const registerExecCommands$1 = (editor) => {
    editor.editorCommands.addCommands({
      'InsertUnorderedList,InsertOrderedList': (command) => {
        editor.getDoc().execCommand(command)
        const listElm = editor.dom.getParent(editor.selection.getNode(), 'ol,ul')
        if (listElm) {
          const listParent = listElm.parentNode
          if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
            const bm = editor.selection.getBookmark()
            editor.dom.split(listParent, listElm)
            editor.selection.moveToBookmark(bm)
          }
        }
      },
    })
  }
  const registerQueryStateCommands = (editor) => {
    editor.editorCommands.addCommands(
      {
        'InsertUnorderedList,InsertOrderedList': (command) => {
          const list = editor.dom.getParent(editor.selection.getNode(), 'ul,ol')
          return (
            list &&
            ((command === 'insertunorderedlist' && list.tagName === 'UL') ||
              (command === 'insertorderedlist' && list.tagName === 'OL'))
          )
        },
      },
      'state'
    )
  }
  const registerCommands$3 = (editor) => {
    registerExecCommands$1(editor)
    registerQueryStateCommands(editor)
  }

  const registerCommands$2 = (editor) => {
    editor.editorCommands.addCommands({
      insertParagraph: () => {
        insertBreak(blockbreak, editor)
      },
      mceInsertNewLine: (_command, _ui, value) => {
        insert(editor, value)
      },
      InsertLineBreak: (_command, _ui, _value) => {
        insertBreak(linebreak, editor)
      },
    })
  }

  const registerCommands$1 = (editor) => {
    editor.editorCommands.addCommands({
      mceSelectNodeDepth: (_command, _ui, value) => {
        let counter = 0
        editor.dom.getParent(
          editor.selection.getNode(),
          (node) => {
            if (node.nodeType === 1 && counter++ === value) {
              editor.selection.select(node)
              return false
            }
          },
          editor.getBody()
        )
      },
      mceSelectNode: (_command, _ui, value) => {
        editor.selection.select(value)
      },
      selectAll: () => {
        const editingHost = editor.dom.getParent(editor.selection.getStart(), isContentEditableTrue$5)
        if (editingHost) {
          const rng = editor.dom.createRng()
          rng.selectNodeContents(editingHost)
          editor.selection.setRng(rng)
        }
      },
    })
  }

  const registerExecCommands = (editor) => {
    editor.editorCommands.addCommands({
      mceRemoveNode: (_command, _ui, value) => {
        const node = value !== null && value !== void 0 ? value : editor.selection.getNode()
        if (node !== editor.getBody()) {
          const bm = editor.selection.getBookmark()
          editor.dom.remove(node, true)
          editor.selection.moveToBookmark(bm)
        }
      },
      mcePrint: () => {
        editor.getWin().print()
      },
      mceFocus: (_command, _ui, value) => {
        focus(editor, value)
      },
      mceToggleVisualAid: () => {
        editor.hasVisual = !editor.hasVisual
        editor.addVisual()
      },
    })
  }
  const registerCommands = (editor) => {
    registerCommands$a(editor)
    registerCommands$9(editor)
    registerCommands$6(editor)
    registerCommands$1(editor)
    registerCommands$8(editor)
    registerCommands$4(editor)
    registerCommands$5(editor)
    registerCommands$2(editor)
    registerCommands$3(editor)
    registerCommands$7(editor)
    registerExecCommands(editor)
  }

  class EditorCommands {
    constructor(editor) {
      this.commands = {
        state: {},
        exec: {},
        value: {},
      }
      this.editor = editor
    }
    execCommand(command, ui, value, args) {
      const editor = this.editor
      const lowerCaseCommand = command.toLowerCase()
      const skipFocus = args === null || args === void 0 ? void 0 : args.skip_focus
      if (editor.removed) {
        return false
      }
      if (lowerCaseCommand !== 'mcefocus') {
        if (!/^(mceAddUndoLevel|mceEndUndoLevel)$/i.test(lowerCaseCommand) && !skipFocus) {
          editor.focus()
        } else {
          restore(editor)
        }
      }
      const eventArgs = editor.dispatch('BeforeExecCommand', {
        command,
        ui,
        value,
      })
      if (eventArgs.isDefaultPrevented()) {
        return false
      }
      const func = this.commands.exec[lowerCaseCommand]
      if (isFunction(func)) {
        func(lowerCaseCommand, ui, value)
        editor.dispatch('ExecCommand', {
          command,
          ui,
          value,
        })
        return true
      }
      return false
    }
    queryCommandState(command) {
      if (this.editor.quirks.isHidden() || this.editor.removed) {
        return false
      }
      const lowerCaseCommand = command.toLowerCase()
      const func = this.commands.state[lowerCaseCommand]
      if (isFunction(func)) {
        return func(lowerCaseCommand)
      }
      return false
    }
    queryCommandValue(command) {
      if (this.editor.quirks.isHidden() || this.editor.removed) {
        return ''
      }
      const lowerCaseCommand = command.toLowerCase()
      const func = this.commands.value[lowerCaseCommand]
      if (isFunction(func)) {
        return func(lowerCaseCommand)
      }
      return ''
    }
    addCommands(commandList, type = 'exec') {
      const commands = this.commands
      each$e(commandList, (callback, command) => {
        each$f(command.toLowerCase().split(','), (command) => {
          commands[type][command] = callback
        })
      })
    }
    addCommand(command, callback, scope) {
      const lowerCaseCommand = command.toLowerCase()
      this.commands.exec[lowerCaseCommand] = (_command, ui, value) =>
        callback.call(scope !== null && scope !== void 0 ? scope : this.editor, ui, value)
    }
    queryCommandSupported(command) {
      const lowerCaseCommand = command.toLowerCase()
      if (this.commands.exec[lowerCaseCommand]) {
        return true
      }
      return false
    }
    addQueryStateHandler(command, callback, scope) {
      this.commands.state[command.toLowerCase()] = () =>
        callback.call(scope !== null && scope !== void 0 ? scope : this.editor)
    }
    addQueryValueHandler(command, callback, scope) {
      this.commands.value[command.toLowerCase()] = () =>
        callback.call(scope !== null && scope !== void 0 ? scope : this.editor)
    }
  }

  const internalContentEditableAttr = 'data-mce-contenteditable'
  const toggleClass = (elm, cls, state) => {
    if (has(elm, cls) && state === false) {
      remove$8(elm, cls)
    } else if (state) {
      add$2(elm, cls)
    }
  }
  const setEditorCommandState = (editor, cmd, state) => {
    try {
      editor.getDoc().execCommand(cmd, false, String(state))
    } catch (ex) {}
  }
  const setContentEditable = (elm, state) => {
    elm.dom.contentEditable = state ? 'true' : 'false'
  }
  const switchOffContentEditableTrue = (elm) => {
    each$f(descendants(elm, '*[contenteditable="true"]'), (elm) => {
      set$2(elm, internalContentEditableAttr, 'true')
      setContentEditable(elm, false)
    })
  }
  const switchOnContentEditableTrue = (elm) => {
    each$f(descendants(elm, `*[${internalContentEditableAttr}="true"]`), (elm) => {
      remove$b(elm, internalContentEditableAttr)
      setContentEditable(elm, true)
    })
  }
  const removeFakeSelection = (editor) => {
    Optional.from(editor.selection.getNode()).each((elm) => {
      elm.removeAttribute('data-mce-selected')
    })
  }
  const restoreFakeSelection = (editor) => {
    editor.selection.setRng(editor.selection.getRng())
  }
  const toggleReadOnly = (editor, state) => {
    const body = SugarElement.fromDom(editor.getBody())
    toggleClass(body, 'mce-content-readonly', state)
    if (state) {
      editor.selection.controlSelection.hideResizeRect()
      editor._selectionOverrides.hideFakeCaret()
      removeFakeSelection(editor)
      editor.readonly = true
      setContentEditable(body, false)
      switchOffContentEditableTrue(body)
    } else {
      editor.readonly = false
      setContentEditable(body, true)
      switchOnContentEditableTrue(body)
      setEditorCommandState(editor, 'StyleWithCSS', false)
      setEditorCommandState(editor, 'enableInlineTableEditing', false)
      setEditorCommandState(editor, 'enableObjectResizing', false)
      if (hasEditorOrUiFocus(editor)) {
        editor.focus()
      }
      restoreFakeSelection(editor)
      editor.nodeChanged()
    }
  }
  const isReadOnly = (editor) => editor.readonly
  const registerFilters = (editor) => {
    editor.parser.addAttributeFilter('contenteditable', (nodes) => {
      if (isReadOnly(editor)) {
        each$f(nodes, (node) => {
          node.attr(internalContentEditableAttr, node.attr('contenteditable'))
          node.attr('contenteditable', 'false')
        })
      }
    })
    editor.serializer.addAttributeFilter(internalContentEditableAttr, (nodes) => {
      if (isReadOnly(editor)) {
        each$f(nodes, (node) => {
          node.attr('contenteditable', node.attr(internalContentEditableAttr))
        })
      }
    })
    editor.serializer.addTempAttr(internalContentEditableAttr)
  }
  const registerReadOnlyContentFilters = (editor) => {
    if (editor.serializer) {
      registerFilters(editor)
    } else {
      editor.on('PreInit', () => {
        registerFilters(editor)
      })
    }
  }
  const isClickEvent = (e) => e.type === 'click'
  const allowedEvents = ['copy']
  const isReadOnlyAllowedEvent = (e) => contains$2(allowedEvents, e.type)
  const getAnchorHrefOpt = (editor, elm) => {
    const isRoot = (elm) => eq(elm, SugarElement.fromDom(editor.getBody()))
    return closest$3(elm, 'a', isRoot).bind((a) => getOpt(a, 'href'))
  }
  const processReadonlyEvents = (editor, e) => {
    if (isClickEvent(e) && !VK.metaKeyPressed(e)) {
      const elm = SugarElement.fromDom(e.target)
      getAnchorHrefOpt(editor, elm).each((href) => {
        e.preventDefault()
        if (/^#/.test(href)) {
          const targetEl = editor.dom.select(`${href},[name="${removeLeading(href, '#')}"]`)
          if (targetEl.length) {
            editor.selection.scrollIntoView(targetEl[0], true)
          }
        } else {
          window.open(
            href,
            '_blank',
            'rel=noopener noreferrer,menubar=yes,toolbar=yes,location=yes,status=yes,resizable=yes,scrollbars=yes'
          )
        }
      })
    } else if (isReadOnlyAllowedEvent(e)) {
      editor.dispatch(e.type, e)
    }
  }
  const registerReadOnlySelectionBlockers = (editor) => {
    editor.on('ShowCaret', (e) => {
      if (isReadOnly(editor)) {
        e.preventDefault()
      }
    })
    editor.on('ObjectSelected', (e) => {
      if (isReadOnly(editor)) {
        e.preventDefault()
      }
    })
  }

  const nativeEvents = Tools.makeMap(
    'focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange ' +
      'mouseout mouseenter mouseleave wheel keydown keypress keyup input beforeinput contextmenu dragstart dragend dragover ' +
      'draggesture dragdrop drop drag submit ' +
      'compositionstart compositionend compositionupdate touchstart touchmove touchend touchcancel',
    ' '
  )
  class EventDispatcher {
    constructor(settings) {
      this.bindings = {}
      this.settings = settings || {}
      this.scope = this.settings.scope || this
      this.toggleEvent = this.settings.toggleEvent || never
    }
    static isNative(name) {
      return !!nativeEvents[name.toLowerCase()]
    }
    fire(name, args) {
      return this.dispatch(name, args)
    }
    dispatch(name, args) {
      const lcName = name.toLowerCase()
      const event = normalize$3(lcName, args !== null && args !== void 0 ? args : {}, this.scope)
      if (this.settings.beforeFire) {
        this.settings.beforeFire(event)
      }
      const handlers = this.bindings[lcName]
      if (handlers) {
        for (let i = 0, l = handlers.length; i < l; i++) {
          const callback = handlers[i]
          if (callback.removed) {
            continue
          }
          if (callback.once) {
            this.off(lcName, callback.func)
          }
          if (event.isImmediatePropagationStopped()) {
            return event
          }
          if (callback.func.call(this.scope, event) === false) {
            event.preventDefault()
            return event
          }
        }
      }
      return event
    }
    on(name, callback, prepend, extra) {
      if (callback === false) {
        callback = never
      }
      if (callback) {
        const wrappedCallback = {
          func: callback,
          removed: false,
        }
        if (extra) {
          Tools.extend(wrappedCallback, extra)
        }
        const names = name.toLowerCase().split(' ')
        let i = names.length
        while (i--) {
          const currentName = names[i]
          let handlers = this.bindings[currentName]
          if (!handlers) {
            handlers = []
            this.toggleEvent(currentName, true)
          }
          if (prepend) {
            handlers = [wrappedCallback, ...handlers]
          } else {
            handlers = [...handlers, wrappedCallback]
          }
          this.bindings[currentName] = handlers
        }
      }
      return this
    }
    off(name, callback) {
      if (name) {
        const names = name.toLowerCase().split(' ')
        let i = names.length
        while (i--) {
          const currentName = names[i]
          let handlers = this.bindings[currentName]
          if (!currentName) {
            each$e(this.bindings, (_value, bindingName) => {
              this.toggleEvent(bindingName, false)
              delete this.bindings[bindingName]
            })
            return this
          }
          if (handlers) {
            if (!callback) {
              handlers.length = 0
            } else {
              const filteredHandlers = partition$2(handlers, (handler) => handler.func === callback)
              handlers = filteredHandlers.fail
              this.bindings[currentName] = handlers
              each$f(filteredHandlers.pass, (handler) => {
                handler.removed = true
              })
            }
            if (!handlers.length) {
              this.toggleEvent(name, false)
              delete this.bindings[currentName]
            }
          }
        }
      } else {
        each$e(this.bindings, (_value, name) => {
          this.toggleEvent(name, false)
        })
        this.bindings = {}
      }
      return this
    }
    once(name, callback, prepend) {
      return this.on(name, callback, prepend, { once: true })
    }
    has(name) {
      name = name.toLowerCase()
      return !(!this.bindings[name] || this.bindings[name].length === 0)
    }
  }

  const getEventDispatcher = (obj) => {
    if (!obj._eventDispatcher) {
      obj._eventDispatcher = new EventDispatcher({
        scope: obj,
        toggleEvent: (name, state) => {
          if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
            obj.toggleNativeEvent(name, state)
          }
        },
      })
    }
    return obj._eventDispatcher
  }
  const Observable = {
    fire(name, args, bubble) {
      return this.dispatch(name, args, bubble)
    },
    dispatch(name, args, bubble) {
      const self = this
      if (self.removed && name !== 'remove' && name !== 'detach') {
        return normalize$3(name.toLowerCase(), args !== null && args !== void 0 ? args : {}, self)
      }
      const dispatcherArgs = getEventDispatcher(self).dispatch(name, args)
      if (bubble !== false && self.parent) {
        let parent = self.parent()
        while (parent && !dispatcherArgs.isPropagationStopped()) {
          parent.dispatch(name, dispatcherArgs, false)
          parent = parent.parent()
        }
      }
      return dispatcherArgs
    },
    on(name, callback, prepend) {
      return getEventDispatcher(this).on(name, callback, prepend)
    },
    off(name, callback) {
      return getEventDispatcher(this).off(name, callback)
    },
    once(name, callback) {
      return getEventDispatcher(this).once(name, callback)
    },
    hasEventListeners(name) {
      return getEventDispatcher(this).has(name)
    },
  }

  const DOM$2 = DOMUtils.DOM
  let customEventRootDelegates
  const getEventTarget = (editor, eventName) => {
    if (eventName === 'selectionchange') {
      return editor.getDoc()
    }
    if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
      return editor.getDoc().documentElement
    }
    const eventRoot = getEventRoot(editor)
    if (eventRoot) {
      if (!editor.eventRoot) {
        editor.eventRoot = DOM$2.select(eventRoot)[0]
      }
      return editor.eventRoot
    }
    return editor.getBody()
  }
  const isListening = (editor) => !editor.hidden && !isReadOnly(editor)
  const fireEvent = (editor, eventName, e) => {
    if (isListening(editor)) {
      editor.dispatch(eventName, e)
    } else if (isReadOnly(editor)) {
      processReadonlyEvents(editor, e)
    }
  }
  const bindEventDelegate = (editor, eventName) => {
    let delegate
    if (!editor.delegates) {
      editor.delegates = {}
    }
    if (editor.delegates[eventName] || editor.removed) {
      return
    }
    const eventRootElm = getEventTarget(editor, eventName)
    if (getEventRoot(editor)) {
      if (!customEventRootDelegates) {
        customEventRootDelegates = {}
        editor.editorManager.on('removeEditor', () => {
          if (!editor.editorManager.activeEditor) {
            if (customEventRootDelegates) {
              each$e(customEventRootDelegates, (_value, name) => {
                editor.dom.unbind(getEventTarget(editor, name))
              })
              customEventRootDelegates = null
            }
          }
        })
      }
      if (customEventRootDelegates[eventName]) {
        return
      }
      delegate = (e) => {
        const target = e.target
        const editors = editor.editorManager.get()
        let i = editors.length
        while (i--) {
          const body = editors[i].getBody()
          if (body === target || DOM$2.isChildOf(target, body)) {
            fireEvent(editors[i], eventName, e)
          }
        }
      }
      customEventRootDelegates[eventName] = delegate
      DOM$2.bind(eventRootElm, eventName, delegate)
    } else {
      delegate = (e) => {
        fireEvent(editor, eventName, e)
      }
      DOM$2.bind(eventRootElm, eventName, delegate)
      editor.delegates[eventName] = delegate
    }
  }
  const EditorObservable = {
    ...Observable,
    bindPendingEventDelegates() {
      const self = this
      Tools.each(self._pendingNativeEvents, (name) => {
        bindEventDelegate(self, name)
      })
    },
    toggleNativeEvent(name, state) {
      const self = this
      if (name === 'focus' || name === 'blur') {
        return
      }
      if (self.removed) {
        return
      }
      if (state) {
        if (self.initialized) {
          bindEventDelegate(self, name)
        } else {
          if (!self._pendingNativeEvents) {
            self._pendingNativeEvents = [name]
          } else {
            self._pendingNativeEvents.push(name)
          }
        }
      } else if (self.initialized) {
        self.dom.unbind(getEventTarget(self, name), name, self.delegates[name])
        delete self.delegates[name]
      }
    },
    unbindAllNativeEvents() {
      const self = this
      const body = self.getBody()
      const dom = self.dom
      if (self.delegates) {
        each$e(self.delegates, (value, name) => {
          self.dom.unbind(getEventTarget(self, name), name, value)
        })
        delete self.delegates
      }
      if (!self.inline && body && dom) {
        body.onload = null
        dom.unbind(self.getWin())
        dom.unbind(self.getDoc())
      }
      if (dom) {
        dom.unbind(body)
        dom.unbind(self.getContainer())
      }
    },
  }

  const stringListProcessor = (value) => {
    if (isString(value)) {
      return {
        value: value.split(/[ ,]/),
        valid: true,
      }
    } else if (isArrayOf(value, isString)) {
      return {
        value,
        valid: true,
      }
    } else {
      return {
        valid: false,
        message: `The value must be a string[] or a comma/space separated string.`,
      }
    }
  }
  const getBuiltInProcessor = (type) => {
    const validator = (() => {
      switch (type) {
        case 'array':
          return isArray$1
        case 'boolean':
          return isBoolean
        case 'function':
          return isFunction
        case 'number':
          return isNumber
        case 'object':
          return isObject
        case 'string':
          return isString
        case 'string[]':
          return stringListProcessor
        case 'object[]':
          return (val) => isArrayOf(val, isObject)
        case 'regexp':
          return (val) => is$4(val, RegExp)
      }
    })()
    return (value) => processValue(value, validator, `The value must be a ${type}.`)
  }
  const isBuiltInSpec = (spec) => isString(spec.processor)
  const getErrorMessage = (message, result) => {
    const additionalText = isEmpty$3(result.message) ? '' : `. ${result.message}`
    return message + additionalText
  }
  const isValidResult = (result) => result.valid
  const processValue = (value, processor, message = '') => {
    const result = processor(value)
    if (isBoolean(result)) {
      return result
        ? {
            value: value,
            valid: true,
          }
        : {
            valid: false,
            message,
          }
    } else {
      return result
    }
  }
  const processDefaultValue = (name, defaultValue, processor) => {
    if (!isUndefined(defaultValue)) {
      const result = processValue(defaultValue, processor)
      if (isValidResult(result)) {
        return result.value
      } else {
        console.error(getErrorMessage(`Invalid default value passed for the "${name}" option`, result))
      }
    }
    return undefined
  }
  const create$5 = (editor, initialOptions) => {
    const registry = {}
    const values = {}
    const setValue = (name, value, processor) => {
      const result = processValue(value, processor)
      if (isValidResult(result)) {
        values[name] = result.value
        return true
      } else {
        console.warn(getErrorMessage(`Invalid value passed for the ${name} option`, result))
        return false
      }
    }
    const register = (name, spec) => {
      const processor = isBuiltInSpec(spec) ? getBuiltInProcessor(spec.processor) : spec.processor
      const defaultValue = processDefaultValue(name, spec.default, processor)
      registry[name] = {
        ...spec,
        default: defaultValue,
        processor,
      }
      const initValue = get$a(values, name).orThunk(() => get$a(initialOptions, name))
      initValue.each((value) => setValue(name, value, processor))
    }
    const isRegistered = (name) => has$2(registry, name)
    const get = (name) =>
      get$a(values, name)
        .orThunk(() => get$a(registry, name).map((spec) => spec.default))
        .getOrUndefined()
    const set = (name, value) => {
      if (!isRegistered(name)) {
        console.warn(
          `"${name}" is not a registered option. Ensure the option has been registered before setting a value.`
        )
        return false
      } else {
        const spec = registry[name]
        if (spec.immutable) {
          console.error(`"${name}" is an immutable option and cannot be updated`)
          return false
        } else {
          return setValue(name, value, spec.processor)
        }
      }
    }
    const unset = (name) => {
      const registered = isRegistered(name)
      if (registered) {
        delete values[name]
      }
      return registered
    }
    const isSet = (name) => has$2(values, name)
    return {
      register,
      isRegistered,
      get,
      set,
      unset,
      isSet,
    }
  }

  const defaultModes = ['design', 'readonly']
  const switchToMode = (editor, activeMode, availableModes, mode) => {
    const oldMode = availableModes[activeMode.get()]
    const newMode = availableModes[mode]
    try {
      newMode.activate()
    } catch (e) {
      console.error(`problem while activating editor mode ${mode}:`, e)
      return
    }
    oldMode.deactivate()
    if (oldMode.editorReadOnly !== newMode.editorReadOnly) {
      toggleReadOnly(editor, newMode.editorReadOnly)
    }
    activeMode.set(mode)
    fireSwitchMode(editor, mode)
  }
  const setMode = (editor, availableModes, activeMode, mode) => {
    if (mode === activeMode.get()) {
      return
    } else if (!has$2(availableModes, mode)) {
      throw new Error(`Editor mode '${mode}' is invalid`)
    }
    if (editor.initialized) {
      switchToMode(editor, activeMode, availableModes, mode)
    } else {
      editor.on('init', () => switchToMode(editor, activeMode, availableModes, mode))
    }
  }
  const registerMode = (availableModes, mode, api) => {
    if (contains$2(defaultModes, mode)) {
      throw new Error(`Cannot override default mode ${mode}`)
    }
    return {
      ...availableModes,
      [mode]: {
        ...api,
        deactivate: () => {
          try {
            api.deactivate()
          } catch (e) {
            console.error(`problem while deactivating editor mode ${mode}:`, e)
          }
        },
      },
    }
  }

  const create$4 = (editor) => {
    const activeMode = Cell('design')
    const availableModes = Cell({
      design: {
        activate: noop,
        deactivate: noop,
        editorReadOnly: false,
      },
      readonly: {
        activate: noop,
        deactivate: noop,
        editorReadOnly: true,
      },
    })
    registerReadOnlyContentFilters(editor)
    registerReadOnlySelectionBlockers(editor)
    return {
      isReadOnly: () => isReadOnly(editor),
      set: (mode) => setMode(editor, availableModes.get(), activeMode, mode),
      get: () => activeMode.get(),
      register: (mode, api) => {
        availableModes.set(registerMode(availableModes.get(), mode, api))
      },
    }
  }

  const each$2 = Tools.each,
    explode = Tools.explode
  const keyCodeLookup = {
    f1: 112,
    f2: 113,
    f3: 114,
    f4: 115,
    f5: 116,
    f6: 117,
    f7: 118,
    f8: 119,
    f9: 120,
    f10: 121,
    f11: 122,
    f12: 123,
  }
  const modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access')
  const parseShortcut = (pattern) => {
    let key
    const shortcut = {}
    const isMac = Env.os.isMacOS() || Env.os.isiOS()
    each$2(explode(pattern.toLowerCase(), '+'), (value) => {
      if (value in modifierNames) {
        shortcut[value] = true
      } else {
        if (/^[0-9]{2,}$/.test(value)) {
          shortcut.keyCode = parseInt(value, 10)
        } else {
          shortcut.charCode = value.charCodeAt(0)
          shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0)
        }
      }
    })
    const id = [shortcut.keyCode]
    for (key in modifierNames) {
      if (shortcut[key]) {
        id.push(key)
      } else {
        shortcut[key] = false
      }
    }
    shortcut.id = id.join(',')
    if (shortcut.access) {
      shortcut.alt = true
      if (isMac) {
        shortcut.ctrl = true
      } else {
        shortcut.shift = true
      }
    }
    if (shortcut.meta) {
      if (isMac) {
        shortcut.meta = true
      } else {
        shortcut.ctrl = true
        shortcut.meta = false
      }
    }
    return shortcut
  }
  class Shortcuts {
    constructor(editor) {
      this.shortcuts = {}
      this.pendingPatterns = []
      this.editor = editor
      const self = this
      editor.on('keyup keypress keydown', (e) => {
        if ((self.hasModifier(e) || self.isFunctionKey(e)) && !e.isDefaultPrevented()) {
          each$2(self.shortcuts, (shortcut) => {
            if (self.matchShortcut(e, shortcut)) {
              self.pendingPatterns = shortcut.subpatterns.slice(0)
              if (e.type === 'keydown') {
                self.executeShortcutAction(shortcut)
              }
              return true
            }
          })
          if (self.matchShortcut(e, self.pendingPatterns[0])) {
            if (self.pendingPatterns.length === 1) {
              if (e.type === 'keydown') {
                self.executeShortcutAction(self.pendingPatterns[0])
              }
            }
            self.pendingPatterns.shift()
          }
        }
      })
    }
    add(pattern, desc, cmdFunc, scope) {
      const self = this
      const func = self.normalizeCommandFunc(cmdFunc)
      each$2(explode(Tools.trim(pattern)), (pattern) => {
        const shortcut = self.createShortcut(pattern, desc, func, scope)
        self.shortcuts[shortcut.id] = shortcut
      })
      return true
    }
    remove(pattern) {
      const shortcut = this.createShortcut(pattern)
      if (this.shortcuts[shortcut.id]) {
        delete this.shortcuts[shortcut.id]
        return true
      }
      return false
    }
    normalizeCommandFunc(cmdFunc) {
      const self = this
      const cmd = cmdFunc
      if (typeof cmd === 'string') {
        return () => {
          self.editor.execCommand(cmd, false, null)
        }
      } else if (Tools.isArray(cmd)) {
        return () => {
          self.editor.execCommand(cmd[0], cmd[1], cmd[2])
        }
      } else {
        return cmd
      }
    }
    createShortcut(pattern, desc, cmdFunc, scope) {
      const shortcuts = Tools.map(explode(pattern, '>'), parseShortcut)
      shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
        func: cmdFunc,
        scope: scope || this.editor,
      })
      return Tools.extend(shortcuts[0], {
        desc: this.editor.translate(desc),
        subpatterns: shortcuts.slice(1),
      })
    }
    hasModifier(e) {
      return e.altKey || e.ctrlKey || e.metaKey
    }
    isFunctionKey(e) {
      return e.type === 'keydown' && e.keyCode >= 112 && e.keyCode <= 123
    }
    matchShortcut(e, shortcut) {
      if (!shortcut) {
        return false
      }
      if (shortcut.ctrl !== e.ctrlKey || shortcut.meta !== e.metaKey) {
        return false
      }
      if (shortcut.alt !== e.altKey || shortcut.shift !== e.shiftKey) {
        return false
      }
      if (e.keyCode === shortcut.keyCode || (e.charCode && e.charCode === shortcut.charCode)) {
        e.preventDefault()
        return true
      }
      return false
    }
    executeShortcutAction(shortcut) {
      return shortcut.func ? shortcut.func.call(shortcut.scope) : null
    }
  }

  const create$3 = () => {
    const buttons = {}
    const menuItems = {}
    const popups = {}
    const icons = {}
    const contextMenus = {}
    const contextToolbars = {}
    const sidebars = {}
    const add = (collection, type) => (name, spec) =>
      (collection[name.toLowerCase()] = {
        ...spec,
        type,
      })
    const addIcon = (name, svgData) => (icons[name.toLowerCase()] = svgData)
    return {
      addButton: add(buttons, 'button'),
      addGroupToolbarButton: add(buttons, 'grouptoolbarbutton'),
      addToggleButton: add(buttons, 'togglebutton'),
      addMenuButton: add(buttons, 'menubutton'),
      addSplitButton: add(buttons, 'splitbutton'),
      addMenuItem: add(menuItems, 'menuitem'),
      addNestedMenuItem: add(menuItems, 'nestedmenuitem'),
      addToggleMenuItem: add(menuItems, 'togglemenuitem'),
      addAutocompleter: add(popups, 'autocompleter'),
      addContextMenu: add(contextMenus, 'contextmenu'),
      addContextToolbar: add(contextToolbars, 'contexttoolbar'),
      addContextForm: add(contextToolbars, 'contextform'),
      addSidebar: add(sidebars, 'sidebar'),
      addIcon,
      getAll: () => ({
        buttons,
        menuItems,
        icons,
        popups,
        contextMenus,
        contextToolbars,
        sidebars,
      }),
    }
  }

  const registry = () => {
    const bridge = create$3()
    return {
      addAutocompleter: bridge.addAutocompleter,
      addButton: bridge.addButton,
      addContextForm: bridge.addContextForm,
      addContextMenu: bridge.addContextMenu,
      addContextToolbar: bridge.addContextToolbar,
      addIcon: bridge.addIcon,
      addMenuButton: bridge.addMenuButton,
      addMenuItem: bridge.addMenuItem,
      addNestedMenuItem: bridge.addNestedMenuItem,
      addSidebar: bridge.addSidebar,
      addSplitButton: bridge.addSplitButton,
      addToggleButton: bridge.addToggleButton,
      addGroupToolbarButton: bridge.addGroupToolbarButton,
      addToggleMenuItem: bridge.addToggleMenuItem,
      getAll: bridge.getAll,
    }
  }

  const DOM$1 = DOMUtils.DOM
  const extend = Tools.extend,
    each$1 = Tools.each
  class Editor {
    constructor(id, options, editorManager) {
      this.plugins = {}
      this.contentCSS = []
      this.contentStyles = []
      this.loadedCSS = {}
      this.isNotDirty = false
      this.editorManager = editorManager
      this.documentBaseUrl = editorManager.documentBaseURL
      extend(this, EditorObservable)
      const self = this
      this.id = id
      this.hidden = false
      const normalizedOptions = normalizeOptions(editorManager.defaultOptions, options)
      this.options = create$5(self, normalizedOptions)
      register$7(self)
      const getOption = this.options.get
      if (getOption('deprecation_warnings')) {
        logWarnings(options, normalizedOptions)
      }
      const suffix = getOption('suffix')
      if (suffix) {
        editorManager.suffix = suffix
      }
      this.suffix = editorManager.suffix
      const baseUrl = getOption('base_url')
      if (baseUrl) {
        editorManager._setBaseUrl(baseUrl)
      }
      this.baseUri = editorManager.baseURI
      const referrerPolicy = getReferrerPolicy(self)
      if (referrerPolicy) {
        ScriptLoader.ScriptLoader._setReferrerPolicy(referrerPolicy)
        DOMUtils.DOM.styleSheetLoader._setReferrerPolicy(referrerPolicy)
      }
      AddOnManager.languageLoad = getOption('language_load')
      AddOnManager.baseURL = editorManager.baseURL
      this.setDirty(false)
      this.documentBaseURI = new URI(getDocumentBaseUrl(self), { base_uri: this.baseUri })
      this.baseURI = this.baseUri
      this.inline = isInline(self)
      this.shortcuts = new Shortcuts(this)
      this.editorCommands = new EditorCommands(this)
      registerCommands(this)
      const cacheSuffix = getOption('cache_suffix')
      if (cacheSuffix) {
        Env.cacheSuffix = cacheSuffix.replace(/^[\?\&]+/, '')
      }
      this.ui = {
        registry: registry(),
        styleSheetLoader: undefined,
        show: noop,
        hide: noop,
        setEnabled: noop,
        isEnabled: always,
      }
      this.mode = create$4(self)
      editorManager.dispatch('SetupEditor', { editor: this })
      const setupCallback = getSetupCallback(self)
      if (isFunction(setupCallback)) {
        setupCallback.call(self, self)
      }
    }
    render() {
      render(this)
    }
    focus(skipFocus) {
      this.execCommand('mceFocus', false, skipFocus)
    }
    hasFocus() {
      return hasFocus(this)
    }
    translate(text) {
      return I18n.translate(text)
    }
    getParam(name, defaultVal, type) {
      const options = this.options
      if (!options.isRegistered(name)) {
        if (isNonNullable(type)) {
          options.register(name, {
            processor: type,
            default: defaultVal,
          })
        } else {
          options.register(name, {
            processor: always,
            default: defaultVal,
          })
        }
      }
      return !options.isSet(name) && !isUndefined(defaultVal) ? defaultVal : options.get(name)
    }
    hasPlugin(name, loaded) {
      const hasPlugin = contains$2(getPlugins(this), name)
      if (hasPlugin) {
        return loaded ? PluginManager.get(name) !== undefined : true
      } else {
        return false
      }
    }
    nodeChanged(args) {
      this._nodeChangeDispatcher.nodeChanged(args)
    }
    addCommand(name, callback, scope) {
      this.editorCommands.addCommand(name, callback, scope)
    }
    addQueryStateHandler(name, callback, scope) {
      this.editorCommands.addQueryStateHandler(name, callback, scope)
    }
    addQueryValueHandler(name, callback, scope) {
      this.editorCommands.addQueryValueHandler(name, callback, scope)
    }
    addShortcut(pattern, desc, cmdFunc, scope) {
      this.shortcuts.add(pattern, desc, cmdFunc, scope)
    }
    execCommand(cmd, ui, value, args) {
      return this.editorCommands.execCommand(cmd, ui, value, args)
    }
    queryCommandState(cmd) {
      return this.editorCommands.queryCommandState(cmd)
    }
    queryCommandValue(cmd) {
      return this.editorCommands.queryCommandValue(cmd)
    }
    queryCommandSupported(cmd) {
      return this.editorCommands.queryCommandSupported(cmd)
    }
    show() {
      const self = this
      if (self.hidden) {
        self.hidden = false
        if (self.inline) {
          self.getBody().contentEditable = 'true'
        } else {
          DOM$1.show(self.getContainer())
          DOM$1.hide(self.id)
        }
        self.load()
        self.dispatch('show')
      }
    }
    hide() {
      const self = this
      if (!self.hidden) {
        self.save()
        if (self.inline) {
          self.getBody().contentEditable = 'false'
          if (self === self.editorManager.focusedEditor) {
            self.editorManager.focusedEditor = null
          }
        } else {
          DOM$1.hide(self.getContainer())
          DOM$1.setStyle(self.id, 'display', self.orgDisplay)
        }
        self.hidden = true
        self.dispatch('hide')
      }
    }
    isHidden() {
      return this.hidden
    }
    setProgressState(state, time) {
      this.dispatch('ProgressState', {
        state,
        time,
      })
    }
    load(args) {
      const self = this
      let elm = self.getElement(),
        html
      if (self.removed) {
        return ''
      }
      if (elm) {
        args = args || {}
        args.load = true
        const value = isTextareaOrInput(elm) ? elm.value : elm.innerHTML
        html = self.setContent(value, args)
        args.element = elm
        if (!args.no_events) {
          self.dispatch('LoadContent', args)
        }
        args.element = elm = null
        return html
      }
    }
    save(args) {
      const self = this
      let elm = self.getElement(),
        html,
        form
      if (!elm || !self.initialized || self.removed) {
        return
      }
      args = args || {}
      args.save = true
      args.element = elm
      html = args.content = self.getContent(args)
      if (!args.no_events) {
        self.dispatch('SaveContent', args)
      }
      if (args.format === 'raw') {
        self.dispatch('RawSaveContent', args)
      }
      html = args.content
      if (!isTextareaOrInput(elm)) {
        if (args.is_removing || !self.inline) {
          elm.innerHTML = html
        }
        if ((form = DOM$1.getParent(self.id, 'form'))) {
          each$1(form.elements, (elm) => {
            if (elm.name === self.id) {
              elm.value = html
              return false
            }
          })
        }
      } else {
        elm.value = html
      }
      args.element = elm = null
      if (args.set_dirty !== false) {
        self.setDirty(false)
      }
      return html
    }
    setContent(content, args) {
      return setContent(this, content, args)
    }
    getContent(args) {
      return getContent(this, args)
    }
    insertContent(content, args) {
      if (args) {
        content = extend({ content }, args)
      }
      this.execCommand('mceInsertContent', false, content)
    }
    resetContent(initialContent) {
      if (initialContent === undefined) {
        setContent(this, this.startContent, { format: 'raw' })
      } else {
        setContent(this, initialContent)
      }
      this.undoManager.reset()
      this.setDirty(false)
      this.nodeChanged()
    }
    isDirty() {
      return !this.isNotDirty
    }
    setDirty(state) {
      const oldState = !this.isNotDirty
      this.isNotDirty = !state
      if (state && state !== oldState) {
        this.dispatch('dirty')
      }
    }
    getContainer() {
      const self = this
      if (!self.container) {
        self.container = DOM$1.get(self.editorContainer || self.id + '_parent')
      }
      return self.container
    }
    getContentAreaContainer() {
      return this.contentAreaContainer
    }
    getElement() {
      if (!this.targetElm) {
        this.targetElm = DOM$1.get(this.id)
      }
      return this.targetElm
    }
    getWin() {
      const self = this
      let elm
      if (!self.contentWindow) {
        elm = self.iframeElement
        if (elm) {
          self.contentWindow = elm.contentWindow
        }
      }
      return self.contentWindow
    }
    getDoc() {
      const self = this
      let win
      if (!self.contentDocument) {
        win = self.getWin()
        if (win) {
          self.contentDocument = win.document
        }
      }
      return self.contentDocument
    }
    getBody() {
      const doc = this.getDoc()
      return this.bodyElement || (doc ? doc.body : null)
    }
    convertURL(url, name, elm) {
      const self = this,
        getOption = self.options.get
      const urlConverterCallback = getUrlConverterCallback(self)
      if (isFunction(urlConverterCallback)) {
        return urlConverterCallback.call(self, url, elm, true, name)
      }
      if (
        !getOption('convert_urls') ||
        (elm && elm.nodeName === 'LINK') ||
        url.indexOf('file:') === 0 ||
        url.length === 0
      ) {
        return url
      }
      if (getOption('relative_urls')) {
        return self.documentBaseURI.toRelative(url)
      }
      url = self.documentBaseURI.toAbsolute(url, getOption('remove_script_host'))
      return url
    }
    addVisual(elm) {
      addVisual(this, elm)
    }
    remove() {
      remove$1(this)
    }
    destroy(automatic) {
      destroy(this, automatic)
    }
    uploadImages() {
      return this.editorUpload.uploadImages()
    }
    _scanForImages() {
      return this.editorUpload.scanForImages()
    }
  }

  const DOM = DOMUtils.DOM
  const each = Tools.each
  let boundGlobalEvents = false
  let beforeUnloadDelegate
  let editors = []
  const globalEventDelegate = (e) => {
    const type = e.type
    each(EditorManager.get(), (editor) => {
      switch (type) {
        case 'scroll':
          editor.dispatch('ScrollWindow', e)
          break
        case 'resize':
          editor.dispatch('ResizeWindow', e)
          break
      }
    })
  }
  const toggleGlobalEvents = (state) => {
    if (state !== boundGlobalEvents) {
      const DOM = DOMUtils.DOM
      if (state) {
        DOM.bind(window, 'resize', globalEventDelegate)
        DOM.bind(window, 'scroll', globalEventDelegate)
      } else {
        DOM.unbind(window, 'resize', globalEventDelegate)
        DOM.unbind(window, 'scroll', globalEventDelegate)
      }
      boundGlobalEvents = state
    }
  }
  const removeEditorFromList = (targetEditor) => {
    const oldEditors = editors
    editors = filter$6(editors, (editor) => {
      return targetEditor !== editor
    })
    if (EditorManager.activeEditor === targetEditor) {
      EditorManager.activeEditor = editors.length > 0 ? editors[0] : null
    }
    if (EditorManager.focusedEditor === targetEditor) {
      EditorManager.focusedEditor = null
    }
    return oldEditors.length !== editors.length
  }
  const purgeDestroyedEditor = (editor) => {
    if (editor && editor.initialized && !(editor.getContainer() || editor.getBody()).parentNode) {
      removeEditorFromList(editor)
      editor.unbindAllNativeEvents()
      editor.destroy(true)
      editor.removed = true
      editor = null
    }
    return editor
  }
  const isQuirksMode = document.compatMode !== 'CSS1Compat'
  const EditorManager = {
    ...Observable,
    baseURI: null,
    baseURL: null,
    defaultOptions: {},
    documentBaseURL: null,
    suffix: null,
    majorVersion: '6',
    minorVersion: '1.2',
    releaseDate: '2022-07-29',
    i18n: I18n,
    activeEditor: null,
    focusedEditor: null,
    setup() {
      const self = this
      let baseURL,
        documentBaseURL,
        suffix = ''
      documentBaseURL = URI.getDocumentBaseUrl(document.location)
      if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
        documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '')
        if (!/[\/\\]$/.test(documentBaseURL)) {
          documentBaseURL += '/'
        }
      }
      const preInit = window.tinymce || window.tinyMCEPreInit
      if (preInit) {
        baseURL = preInit.base || preInit.baseURL
        suffix = preInit.suffix
      } else {
        const scripts = document.getElementsByTagName('script')
        for (let i = 0; i < scripts.length; i++) {
          const src = scripts[i].src || ''
          if (src === '') {
            continue
          }
          const srcScript = src.substring(src.lastIndexOf('/'))
          if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
            if (srcScript.indexOf('.min') !== -1) {
              suffix = '.min'
            }
            baseURL = src.substring(0, src.lastIndexOf('/'))
            break
          }
        }
        if (!baseURL && document.currentScript) {
          const src = document.currentScript.src
          if (src.indexOf('.min') !== -1) {
            suffix = '.min'
          }
          baseURL = src.substring(0, src.lastIndexOf('/'))
        }
      }
      self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL)
      self.documentBaseURL = documentBaseURL
      self.baseURI = new URI(self.baseURL)
      self.suffix = suffix
      setup$v(self)
    },
    overrideDefaults(defaultOptions) {
      const baseUrl = defaultOptions.base_url
      if (baseUrl) {
        this._setBaseUrl(baseUrl)
      }
      const suffix = defaultOptions.suffix
      if (defaultOptions.suffix) {
        this.suffix = suffix
      }
      this.defaultOptions = defaultOptions
      const pluginBaseUrls = defaultOptions.plugin_base_urls
      if (pluginBaseUrls !== undefined) {
        each$e(pluginBaseUrls, (pluginBaseUrl, pluginName) => {
          AddOnManager.PluginManager.urls[pluginName] = pluginBaseUrl
        })
      }
    },
    init(options) {
      const self = this
      let result
      const invalidInlineTargets = Tools.makeMap(
        'area base basefont br col frame hr img input isindex link meta param embed source wbr track ' +
          'colgroup option table tbody tfoot thead tr th td script noscript style textarea video audio iframe object menu',
        ' '
      )
      const isInvalidInlineTarget = (options, elm) =>
        options.inline && elm.tagName.toLowerCase() in invalidInlineTargets
      const createId = (elm) => {
        let id = elm.id
        if (!id) {
          id = get$a(elm, 'name')
            .filter((name) => !DOM.get(name))
            .getOrThunk(DOM.uniqueId)
          elm.setAttribute('id', id)
        }
        return id
      }
      const execCallback = (name) => {
        const callback = options[name]
        if (!callback) {
          return
        }
        return callback.apply(self, [])
      }
      const findTargets = (options) => {
        if (Env.browser.isIE() || Env.browser.isEdge()) {
          initError(
            'TinyMCE does not support the browser you are using. For a list of supported' +
              ' browsers please see: https://www.tiny.cloud/docs/tinymce/6/support/#supportedwebbrowsers'
          )
          return []
        } else if (isQuirksMode) {
          initError(
            'Failed to initialize the editor as the document is not in standards mode. ' +
              'TinyMCE requires standards mode.'
          )
          return []
        } else if (isString(options.selector)) {
          return DOM.select(options.selector)
        } else if (isNonNullable(options.target)) {
          return [options.target]
        } else {
          return []
        }
      }
      let provideResults = (editors) => {
        result = editors
      }
      const initEditors = () => {
        let initCount = 0
        const editors = []
        let targets
        const createEditor = (id, options, targetElm) => {
          const editor = new Editor(id, options, self)
          editors.push(editor)
          editor.on('init', () => {
            if (++initCount === targets.length) {
              provideResults(editors)
            }
          })
          editor.targetElm = editor.targetElm || targetElm
          editor.render()
        }
        DOM.unbind(window, 'ready', initEditors)
        execCallback('onpageload')
        targets = unique$1(findTargets(options))
        Tools.each(targets, (elm) => {
          purgeDestroyedEditor(self.get(elm.id))
        })
        targets = Tools.grep(targets, (elm) => {
          return !self.get(elm.id)
        })
        if (targets.length === 0) {
          provideResults([])
        } else {
          each(targets, (elm) => {
            if (isInvalidInlineTarget(options, elm)) {
              initError('Could not initialize inline editor on invalid inline target element', elm)
            } else {
              createEditor(createId(elm), options, elm)
            }
          })
        }
      }
      DOM.bind(window, 'ready', initEditors)
      return new Promise((resolve) => {
        if (result) {
          resolve(result)
        } else {
          provideResults = (editors) => {
            resolve(editors)
          }
        }
      })
    },
    get(id) {
      if (arguments.length === 0) {
        return editors.slice(0)
      } else if (isString(id)) {
        return find$2(editors, (editor) => {
          return editor.id === id
        }).getOr(null)
      } else if (isNumber(id)) {
        return editors[id] ? editors[id] : null
      } else {
        return null
      }
    },
    add(editor) {
      const self = this
      const existingEditor = self.get(editor.id)
      if (existingEditor === editor) {
        return editor
      }
      if (existingEditor === null) {
        editors.push(editor)
      }
      toggleGlobalEvents(true)
      self.activeEditor = editor
      self.dispatch('AddEditor', { editor })
      if (!beforeUnloadDelegate) {
        beforeUnloadDelegate = (e) => {
          const event = self.dispatch('BeforeUnload')
          if (event.returnValue) {
            e.preventDefault()
            e.returnValue = event.returnValue
            return event.returnValue
          }
        }
        window.addEventListener('beforeunload', beforeUnloadDelegate)
      }
      return editor
    },
    createEditor(id, options) {
      return this.add(new Editor(id, options, this))
    },
    remove(selector) {
      const self = this
      let i, editor
      if (!selector) {
        for (i = editors.length - 1; i >= 0; i--) {
          self.remove(editors[i])
        }
        return
      }
      if (isString(selector)) {
        each(DOM.select(selector), (elm) => {
          editor = self.get(elm.id)
          if (editor) {
            self.remove(editor)
          }
        })
        return
      }
      editor = selector
      if (isNull(self.get(editor.id))) {
        return null
      }
      if (removeEditorFromList(editor)) {
        self.dispatch('RemoveEditor', { editor })
      }
      if (editors.length === 0) {
        window.removeEventListener('beforeunload', beforeUnloadDelegate)
      }
      editor.remove()
      toggleGlobalEvents(editors.length > 0)
      return editor
    },
    execCommand(cmd, ui, value) {
      var _a
      const self = this
      const editorId = isObject(value) ? ((_a = value.id) !== null && _a !== void 0 ? _a : value.index) : value
      switch (cmd) {
        case 'mceAddEditor': {
          if (!self.get(editorId)) {
            const editorOptions = value.options
            new Editor(editorId, editorOptions, self).render()
          }
          return true
        }
        case 'mceRemoveEditor': {
          const editor = self.get(editorId)
          if (editor) {
            editor.remove()
          }
          return true
        }
        case 'mceToggleEditor': {
          const editor = self.get(editorId)
          if (!editor) {
            self.execCommand('mceAddEditor', false, value)
            return true
          }
          if (editor.isHidden()) {
            editor.show()
          } else {
            editor.hide()
          }
          return true
        }
      }
      if (self.activeEditor) {
        return self.activeEditor.execCommand(cmd, ui, value)
      }
      return false
    },
    triggerSave: () => {
      each(editors, (editor) => {
        editor.save()
      })
    },
    addI18n: (code, items) => {
      I18n.add(code, items)
    },
    translate: (text) => {
      return I18n.translate(text)
    },
    setActive(editor) {
      const activeEditor = this.activeEditor
      if (this.activeEditor !== editor) {
        if (activeEditor) {
          activeEditor.dispatch('deactivate', { relatedTarget: editor })
        }
        editor.dispatch('activate', { relatedTarget: activeEditor })
      }
      this.activeEditor = editor
    },
    _setBaseUrl(baseUrl) {
      this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''))
      this.baseURI = new URI(this.baseURL)
    },
  }
  EditorManager.setup()

  const setup = () => {
    const dataValue = value$2()
    const FakeClipboardItem = (items) => ({
      items,
      types: keys(items),
      getType: (type) => get$a(items, type).getOrUndefined(),
    })
    const write = (data) => {
      dataValue.set(data)
    }
    const read = () => dataValue.get().getOrUndefined()
    const clear = dataValue.clear
    return {
      FakeClipboardItem,
      write,
      read,
      clear,
    }
  }
  const FakeClipboard = setup()

  const min = Math.min,
    max = Math.max,
    round = Math.round
  const relativePosition = (rect, targetRect, rel) => {
    let x = targetRect.x
    let y = targetRect.y
    const w = rect.w
    const h = rect.h
    const targetW = targetRect.w
    const targetH = targetRect.h
    const relChars = (rel || '').split('')
    if (relChars[0] === 'b') {
      y += targetH
    }
    if (relChars[1] === 'r') {
      x += targetW
    }
    if (relChars[0] === 'c') {
      y += round(targetH / 2)
    }
    if (relChars[1] === 'c') {
      x += round(targetW / 2)
    }
    if (relChars[3] === 'b') {
      y -= h
    }
    if (relChars[4] === 'r') {
      x -= w
    }
    if (relChars[3] === 'c') {
      y -= round(h / 2)
    }
    if (relChars[4] === 'c') {
      x -= round(w / 2)
    }
    return create$2(x, y, w, h)
  }
  const findBestRelativePosition = (rect, targetRect, constrainRect, rels) => {
    let pos, i
    for (i = 0; i < rels.length; i++) {
      pos = relativePosition(rect, targetRect, rels[i])
      if (
        pos.x >= constrainRect.x &&
        pos.x + pos.w <= constrainRect.w + constrainRect.x &&
        pos.y >= constrainRect.y &&
        pos.y + pos.h <= constrainRect.h + constrainRect.y
      ) {
        return rels[i]
      }
    }
    return null
  }
  const inflate = (rect, w, h) => {
    return create$2(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2)
  }
  const intersect = (rect, cropRect) => {
    const x1 = max(rect.x, cropRect.x)
    const y1 = max(rect.y, cropRect.y)
    const x2 = min(rect.x + rect.w, cropRect.x + cropRect.w)
    const y2 = min(rect.y + rect.h, cropRect.y + cropRect.h)
    if (x2 - x1 < 0 || y2 - y1 < 0) {
      return null
    }
    return create$2(x1, y1, x2 - x1, y2 - y1)
  }
  const clamp = (rect, clampRect, fixedSize) => {
    let x1 = rect.x
    let y1 = rect.y
    let x2 = rect.x + rect.w
    let y2 = rect.y + rect.h
    const cx2 = clampRect.x + clampRect.w
    const cy2 = clampRect.y + clampRect.h
    const underflowX1 = max(0, clampRect.x - x1)
    const underflowY1 = max(0, clampRect.y - y1)
    const overflowX2 = max(0, x2 - cx2)
    const overflowY2 = max(0, y2 - cy2)
    x1 += underflowX1
    y1 += underflowY1
    if (fixedSize) {
      x2 += underflowX1
      y2 += underflowY1
      x1 -= overflowX2
      y1 -= overflowY2
    }
    x2 -= overflowX2
    y2 -= overflowY2
    return create$2(x1, y1, x2 - x1, y2 - y1)
  }
  const create$2 = (x, y, w, h) => {
    return {
      x,
      y,
      w,
      h,
    }
  }
  const fromClientRect = (clientRect) => {
    return create$2(clientRect.left, clientRect.top, clientRect.width, clientRect.height)
  }
  const Rect = {
    inflate,
    relativePosition,
    findBestRelativePosition,
    intersect,
    clamp,
    create: create$2,
    fromClientRect,
  }

  const awaiter = (resolveCb, rejectCb, timeout = 1000) => {
    let done = false
    let timer = null
    const complete =
      (completer) =>
      (...args) => {
        if (!done) {
          done = true
          if (timer !== null) {
            clearTimeout(timer)
            timer = null
          }
          completer.apply(null, args)
        }
      }
    const resolve = complete(resolveCb)
    const reject = complete(rejectCb)
    const start = (...args) => {
      if (!done && timer === null) {
        timer = setTimeout(() => reject.apply(null, args), timeout)
      }
    }
    return {
      start,
      resolve,
      reject,
    }
  }
  const create$1 = () => {
    const tasks = {}
    const resultFns = {}
    const load = (id, url) => {
      const loadErrMsg = `Script at URL "${url}" failed to load`
      const runErrMsg = `Script at URL "${url}" did not call \`tinymce.Resource.add('${id}', data)\` within 1 second`
      if (tasks[id] !== undefined) {
        return tasks[id]
      } else {
        const task = new Promise((resolve, reject) => {
          const waiter = awaiter(resolve, reject)
          resultFns[id] = waiter.resolve
          ScriptLoader.ScriptLoader.loadScript(url).then(
            () => waiter.start(runErrMsg),
            () => waiter.reject(loadErrMsg)
          )
        })
        tasks[id] = task
        return task
      }
    }
    const add = (id, data) => {
      if (resultFns[id] !== undefined) {
        resultFns[id](data)
        delete resultFns[id]
      }
      tasks[id] = Promise.resolve(data)
    }
    const unload = (id) => {
      delete tasks[id]
    }
    return {
      load,
      add,
      unload,
    }
  }
  const Resource = create$1()

  const create = () =>
    (() => {
      let data = {}
      let keys = []
      const storage = {
        getItem: (key) => {
          const item = data[key]
          return item ? item : null
        },
        setItem: (key, value) => {
          keys.push(key)
          data[key] = String(value)
        },
        key: (index) => {
          return keys[index]
        },
        removeItem: (key) => {
          keys = keys.filter((k) => k === key)
          delete data[key]
        },
        clear: () => {
          keys = []
          data = {}
        },
        length: 0,
      }
      Object.defineProperty(storage, 'length', {
        get: () => keys.length,
        configurable: false,
        enumerable: false,
      })
      return storage
    })()

  let localStorage
  try {
    const test = '__storage_test__'
    localStorage = window.localStorage
    localStorage.setItem(test, test)
    localStorage.removeItem(test)
  } catch (e) {
    localStorage = create()
  }
  var LocalStorage = localStorage

  const publicApi = {
    geom: { Rect },
    util: {
      Delay,
      Tools,
      VK,
      URI,
      EventDispatcher,
      Observable,
      I18n,
      LocalStorage,
      ImageUploader,
    },
    dom: {
      EventUtils,
      TreeWalker: DomTreeWalker,
      TextSeeker,
      DOMUtils,
      ScriptLoader,
      RangeUtils,
      Serializer: DomSerializer,
      StyleSheetLoader,
      ControlSelection,
      BookmarkManager,
      Selection: EditorSelection,
      Event: EventUtils.Event,
    },
    html: {
      Styles,
      Entities,
      Node: AstNode,
      Schema,
      DomParser,
      Writer,
      Serializer: HtmlSerializer,
    },
    Env,
    AddOnManager,
    Annotator,
    Formatter,
    UndoManager,
    EditorCommands,
    WindowManager,
    NotificationManager,
    EditorObservable,
    Shortcuts,
    Editor,
    FocusManager,
    EditorManager,
    DOM: DOMUtils.DOM,
    ScriptLoader: ScriptLoader.ScriptLoader,
    PluginManager,
    ThemeManager,
    ModelManager,
    IconManager,
    Resource,
    FakeClipboard,
    trim: Tools.trim,
    isArray: Tools.isArray,
    is: Tools.is,
    toArray: Tools.toArray,
    makeMap: Tools.makeMap,
    each: Tools.each,
    map: Tools.map,
    grep: Tools.grep,
    inArray: Tools.inArray,
    extend: Tools.extend,
    walk: Tools.walk,
    resolve: Tools.resolve,
    explode: Tools.explode,
    _addCacheSuffix: Tools._addCacheSuffix,
  }
  const tinymce = Tools.extend(EditorManager, publicApi)

  const exportToModuleLoaders = (tinymce) => {
    if (typeof module === 'object') {
      try {
        module.exports = tinymce
      } catch (_) {}
    }
  }
  const exportToWindowGlobal = (tinymce) => {
    window.tinymce = tinymce
    window.tinyMCE = tinymce
  }
  exportToWindowGlobal(tinymce)
  exportToModuleLoaders(tinymce)
})()
